sign in with Apple 接入
1.項(xiàng)目配置
- 需要配置具備sign in with Apple功能的profile文件
- xcode項(xiàng)目的 signing and capabilities中需要新增sign in with apple模塊
2.代碼接入
1. 創(chuàng)建登錄按鈕
// 使用系統(tǒng)提供的按鈕,要注意不支持系統(tǒng)版本的處理
if (@available(iOS 13.0, *)) {
ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180, self.view.bounds.size.width - 60, 100);
[appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];
}
這里的sign in with apple按鈕有兩種文本,三種樣式:
typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonType) {
ASAuthorizationAppleIDButtonTypeSignIn,
ASAuthorizationAppleIDButtonTypeContinue,
ASAuthorizationAppleIDButtonTypeSignUp API_AVAILABLE(ios(13.2), macos(10.15.1), tvos(13.1)) API_UNAVAILABLE(watchos),
ASAuthorizationAppleIDButtonTypeDefault = ASAuthorizationAppleIDButtonTypeSignIn,
} NS_SWIFT_NAME(ASAuthorizationAppleIDButton.ButtonType) API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0)) API_UNAVAILABLE(watchos);
typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonStyle) {
ASAuthorizationAppleIDButtonStyleWhite,
ASAuthorizationAppleIDButtonStyleWhiteOutline,
ASAuthorizationAppleIDButtonStyleBlack,
} NS_SWIFT_NAME(ASAuthorizationAppleIDButton.Style) API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0)) API_UNAVAILABLE(watchos);
2. 監(jiān)聽(tīng)按鈕點(diǎn)擊事件,發(fā)送登錄請(qǐng)求
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請(qǐng)求的一種機(jī)制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 創(chuàng)建新的AppleID 授權(quán)請(qǐng)求
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 在用戶授權(quán)期間請(qǐng)求的聯(lián)系信息
appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
// 由ASAuthorizationAppleIDProvider創(chuàng)建的授權(quán)請(qǐng)求 管理授權(quán)請(qǐng)求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
// 設(shè)置授權(quán)控制器通知授權(quán)請(qǐng)求的成功與失敗的代理
authorizationController.delegate = self;
// 設(shè)置提供 展示上下文的代理,在這個(gè)上下文中 系統(tǒng)可以展示授權(quán)界面給用戶
authorizationController.presentationContextProvider = self;
// 開(kāi)始登錄請(qǐng)求
[authorizationController performRequests];
}else{
// 處理不支持系統(tǒng)版本
NSLog(@"該系統(tǒng)版本不可用Apple登錄");
}
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows{
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請(qǐng)求的一種機(jī)制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 授權(quán)請(qǐng)求AppleID
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 為了執(zhí)行鑰匙串憑證分享生成請(qǐng)求的一種機(jī)制
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
// 由ASAuthorizationAppleIDProvider創(chuàng)建的授權(quán)請(qǐng)求 管理授權(quán)請(qǐng)求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
// 設(shè)置授權(quán)控制器通知授權(quán)請(qǐng)求的成功與失敗的代理
authorizationController.delegate = self;
// 設(shè)置提供 展示上下文的代理,在這個(gè)上下文中 系統(tǒng)可以展示授權(quán)界面給用戶
authorizationController.presentationContextProvider = self;
// 在控制器初始化期間啟動(dòng)授權(quán)流
[authorizationController performRequests];
}else{
// 處理不支持系統(tǒng)版本
NSLog(@"該系統(tǒng)版本不可用Apple登錄");
}
}
3. 通過(guò)代理方法監(jiān)聽(tīng)回調(diào)結(jié)果
- 授權(quán)成功
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用戶登錄使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
NSString *user = appleIDCredential.user;
if (user) {
[YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];
}
// 使用過(guò)授權(quán)的,可能獲取不到以下三個(gè)參數(shù)
NSString *familyName = appleIDCredential.fullName.familyName;
NSString *givenName = appleIDCredential.fullName.givenName;
NSString *email = appleIDCredential.email;
NSData *identityToken = appleIDCredential.identityToken;
NSData *authorizationCode = appleIDCredential.authorizationCode;
// 服務(wù)器驗(yàn)證需要使用的參數(shù)
NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding];
NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];
NSLog(@"%@\n\n%@", identityTokenStr, authorizationCodeStr);
// Create an account in your system.
// For the purpose of this demo app, store the userIdentifier in the keychain.
}
}
sign in with Apple授權(quán)成功后,可以拿到以下數(shù)據(jù):
> 1.userId :用戶的唯一標(biāo)識(shí)符,一個(gè)apple ID對(duì)應(yīng)一個(gè)唯一的userId,通過(guò)這個(gè)id可以 在后臺(tái)用作數(shù)據(jù)綁定
> 2.Verification data:后臺(tái)用來(lái)用作身份驗(yàn)證的數(shù)據(jù),具體驗(yàn)證流程參考:https://www.yuque.com/zhanglong/bb0s5d/cxbh7n
> 3.用戶名和郵箱數(shù)據(jù)
- 授權(quán)失敗
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
// Handle error.
NSLog(@"Handle error:%@", error);
NSString *errorMsg = nil;
switch (error.code) {
case ASAuthorizationErrorCanceled:
errorMsg = @"用戶取消了授權(quán)請(qǐng)求";
break;
case ASAuthorizationErrorFailed:
errorMsg = @"授權(quán)請(qǐng)求失敗";
break;
case ASAuthorizationErrorInvalidResponse:
errorMsg = @"授權(quán)請(qǐng)求響應(yīng)無(wú)效";
break;
case ASAuthorizationErrorNotHandled:
errorMsg = @"未能處理授權(quán)請(qǐng)求";
break;
case ASAuthorizationErrorUnknown:
errorMsg = @"授權(quán)請(qǐng)求失敗未知原因";
break;
default:
break;
}
}
4.當(dāng)用戶終止app使用sign in with apple功能或者在設(shè)置中注銷(xiāo)apple ID時(shí),需要做退出登錄處理,此時(shí)需要對(duì)此行為做監(jiān)聽(tīng),具體是在進(jìn)入應(yīng)用和后臺(tái)返回前臺(tái)時(shí),需要用到userID去查詢此時(shí)賬戶的登錄狀態(tài),具體代碼如下:
- (void)appleLoginStatueChange{
if (@available(iOS 13.0, *)) {
// A mechanism for generating requests to authenticate users based on their Apple ID.
// 基于用戶的Apple ID 生成授權(quán)用戶請(qǐng)求的機(jī)制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 注意 存儲(chǔ)用戶標(biāo)識(shí)信息需要使用鑰匙串來(lái)存儲(chǔ) 這里筆者簡(jiǎn)單期間 使用NSUserDefaults 做的簡(jiǎn)單示例
NSString *userIdentifier = [YostarKeychain load:KEYCHAIN_IDENTIFIER(@"userIdentifier")];
NSLog(@"observeAuthticationState----:%@",userIdentifier);
if (userIdentifier) {
NSString* __block errorMsg = nil;
//Returns the credential state for the given user in a completion handler.
// 在回調(diào)中返回用戶的授權(quán)狀態(tài)
[appleIDProvider getCredentialStateForUserID:userIdentifier completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
switch (credentialState) {
// 蘋(píng)果證書(shū)的授權(quán)狀態(tài)
case ASAuthorizationAppleIDProviderCredentialRevoked:
// 蘋(píng)果授權(quán)憑證失效 -> 此時(shí)是用戶在設(shè)置中終止了此App的sign in with Apple功能
errorMsg = @"蘋(píng)果授權(quán)憑證失效";
// 需要重新登錄
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
// 蘋(píng)果授權(quán)憑證狀態(tài)良好
errorMsg = @"蘋(píng)果授權(quán)憑證狀態(tài)良好";
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
// 未發(fā)現(xiàn)蘋(píng)果授權(quán)憑證 -> 此時(shí)是注銷(xiāo)apple ID的時(shí)候
// 需要重新登錄
errorMsg = @"未發(fā)現(xiàn)蘋(píng)果授權(quán)憑證";
break;
// 可以引導(dǎo)用戶重新登錄
case ASAuthorizationAppleIDProviderCredentialTransferred:
errorMsg = @"蘋(píng)果授權(quán)信息變動(dòng)";
break;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"SignInWithApple授權(quán)狀態(tài)變化情況");
NSLog(@"%@", errorMsg);
});
}];
}
}
}
5.后臺(tái)驗(yàn)證
**主要流程是客戶端拿到userId, identityToken, authorizationCode后傳給后臺(tái),后臺(tái)拼接參數(shù)調(diào)用接口:https://appleid.apple.com/auth/token去蘋(píng)果服務(wù)器請(qǐng)求認(rèn)證。
1. 參數(shù)的生成
client_id: 為app的 bundle identifier
code:為手機(jī)端獲取到的 authorizationCode 信息
grant_type:傳入固定字符串 authorization_code
client_secret:需要自己我們通過(guò)私鑰生成
2.client_secret的生成
1.登錄開(kāi)發(fā)者后臺(tái),選中keys,創(chuàng)建 privateKey,獲取到 Key ID 和 私鑰,創(chuàng)建完之后把私鑰下載下來(lái),并保存好,
注意,私鑰只能下載一次2.拿到上面所有信息之后,可以通過(guò)如下代碼生成 client_secret ,代碼為 Ruby 代碼,確保已安裝ruby環(huán)境。
require "jwt"
key_file = "Path to the private key"
team_id = "Your Team ID"
client_id = "Your App Bundle ID"
key_id = "The Key ID of the private key"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.
private_key = OpenSSL::PKey::EC.new IO.read key_file
token = JWT.encode(
{
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 86400 * validity_period,
aud: "https://appleid.apple.com",
sub: client_id
},
private_key,
"ES256",
header_fields=
{
kid: key_id
}
)
puts token
3.創(chuàng)建文件 secret_gen.rb ,把上面代碼粘貼進(jìn)去,執(zhí)行 ruby secret_gen.rb 即可生成 client_secret
代碼中這個(gè) key_file 需要指定剛才下載的文件的地址
3.調(diào)用接口:https://appleid.apple.com/auth/token
返回?cái)?shù)據(jù)如下:
{
"access_token": "一個(gè)token,此處省略",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "一個(gè)token,此處省略",
"id_token": "結(jié)果是JWT,字符串形式,此處省略"
}
服務(wù)器拿到相應(yīng)結(jié)果,其中 id_token 也是 JWT 數(shù)據(jù),decode 出 payload 部分如下
{
"iss": "https://appleid.apple.com",
"aud": "這個(gè)對(duì)應(yīng)app的bundleid",
"exp": 1567494694,
"iat": 1567494094,
"sub": "這個(gè)字段和手機(jī)端獲取的user信息相同",
"at_hash": "nRYP2wGXBGT0bIYWibx4Yg",
"auth_time": 1567494094
}
其中 aud 部分與你的app的bundleID一致, sub 部分即與手機(jī)端獲得的 user 一致,服務(wù)器端通過(guò)對(duì)比 sub 字段信息是否與手機(jī)端上傳的 user 信息一致來(lái)確定是否成功登錄.
更詳細(xì)的接入信息請(qǐng)參考:
https://www.yuque.com/zhanglong/bb0s5d/cxbh7n
http://www.itdecent.cn/p/e1284bd8c72a
facebook登錄接入(SDK6.2.0)
1.首先進(jìn)入facebook開(kāi)發(fā)者官方網(wǎng)站
facebook開(kāi)發(fā)者官網(wǎng):https://developers.facebook.com/?no_redirect=1
并且成為其開(kāi)發(fā)者。
2.創(chuàng)建應(yīng)用,獲取應(yīng)用編號(hào)
3.進(jìn)入facebook集成登錄模塊
facebook登錄集成文檔:https://developers.facebook.com/docs/facebook-login/ios?sdk=fbsdk
- 1.選擇需要應(yīng)用
- 2.選擇集成方式,此處建議使用cocoapods方式集成,官方提供下載的SDK缺少Bolts庫(kù),會(huì)導(dǎo)致集成錯(cuò)誤
- 3.在 Facebook 注冊(cè)和配置您的應(yīng)用,配置bundle ID
- 4.配置工程項(xiàng)目,在xocde項(xiàng)目的info.plist中配置:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array> <string>fb2516056732057358</string> </array>
</dict>
</array>
<key>FacebookAppID</key>
<string>2516056732057358</string>
<key>FacebookDisplayName</key>
<string>changeDemo</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fbapi20130214</string>
<string>fbapi20130410</string>
<string>fbapi20130702</string>
<string>fbapi20131010</string>
<string>fbapi20131219</string>
<string>fbapi20140410</string>
<string>fbapi20140116</string>
<string>fbapi20150313</string>
<string>fbapi20150629</string>
<string>fbapi20160328</string>
<string>fbauth</string>
<string>fb-messenger-share-api</string>
<string>fbauth2</string>
<string>fbshareextension</string>
</array>
- 5.在appdelegate.m文件中導(dǎo)入:#import < FBSDKCoreKit/FBSDKCoreKit.h>,此時(shí)不考慮SceneDelegate存在的情況.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[ApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
// Add any custom logic here.
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
BOOL handled = [[ApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey] annotation:options[UIApplicationOpenURLOptionsAnnotationKey] ];
// Add any custom logic here.
return handled;
}
如果應(yīng)用中存在SceneDelegate,那么還需配置一下代碼:
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
UIOpenURLContext *openURLContext = URLContexts.allObjects.firstObject;
if (openURLContext) {
[[ApplicationDelegate sharedInstance] application:UIApplication.sharedApplication openURL:openURLContext.URL sourceApplication:openURLContext.options.sourceApplication annotation:openURLContext.options.annotation];
}
// Add any custom logic here.
}
-
6.將facebook登錄代碼接入到app中:
- 使用facebook定制化FBSDKLoginButton按鈕登錄,此按鈕會(huì)根據(jù)用戶登錄和登出自動(dòng)更換文本樣式
FBSDKLoginButton *loginButton = [[FBSDKLoginButton alloc] init]; // Optional: Place the button in the center of your view. loginButton.center = self.view.center; loginButton.permissions = @[@"public_profile", @"email"]; loginButton.delegate = self; [self.view addSubview:loginButton];通過(guò)FBSDKLoginButtonDelegate代理監(jiān)聽(tīng)登錄的狀態(tài):- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(nullable FBSDKLoginManagerLoginResult *)result error:(nullable NSError *)error{ if (result.token) { NSLog(@"登錄成功,userID = %@",result.token.userID); }else{ NSLog(@"登錄失敗"); } }- (void)loginButtonDidLogOut:(FBSDKLoginButton *)loginButton{ NSLog(@"---退出登錄"); }- 使用FBSDKLoginManager登錄
[self.loginManager logInWithPermissions:@[@"public_profile", @"email"] fromViewController:self handler:^(FBSDKLoginManagerLoginResult * _Nullable result, NSError * _Nullable error) { if (result.token) { NSLog(@"登錄成功,userID = %@",result.token.userID); }else{ NSLog(@"登錄失敗"); } }];// 退出登錄 [self.loginManager logOut];- 判斷當(dāng)前用戶是否登錄:
if ([FBSDKAccessToken currentAccessToken]) { // User is logged in, do work such as go to next view controller. }
facebook登錄成功,最終會(huì)獲取到FBSDKAccessToken類(lèi)型的模型,如下:
@property (class, nonatomic, copy, nullable) FBSDKAccessToken *currentAccessToken;
@property (nonatomic, copy, readonly) NSDate *expirationDate;
@property (nonatomic, copy, readonly) NSDate *refreshDate;
// 身份驗(yàn)證的令牌
@property (nonatomic, copy, readonly) NSString *tokenString;// 用戶唯一id
@property (nonatomic, copy, readonly) NSString *userID;
這里我們需要傳給后臺(tái)的就是,userID和tokenString
經(jīng)測(cè)試發(fā)現(xiàn)最新的facebook不支持facebook App授權(quán)登錄,僅支持應(yīng)用內(nèi)跳轉(zhuǎn)到網(wǎng)頁(yè)登錄