蘋果推出了 Sign in with Apple 功能(支持三方登陸的也必須支持蘋果的)。
快到最后時限了還是搞一下吧,沖沖沖:
流程圖:

一、配置
1、需要在蘋果后台打開該選項,並且重新生成Profiles配置文件,並安裝到Xcode


2、服務端驗證需要的文件,一個是私鑰文件(.p8),一個是config.json文件(這個后面說)
先搞私鑰文件:
key->添加->重命名一下,選中sign in with Apple->單擊Configure按鈕,然后選擇你先前創建的Primary App ID
保存之后,Apple將為你生成一個新的私鑰,並讓你僅下載一次,請確保你保存了此文件,
因為以后你將無法再次將其取回!你下載的文件將以.p8結尾,可以將其重命名為key.txt以便在后續步驟中更輕松地使用
另外記一下你的keyID 后台會用到,就不用再來看了,如果兩個APP維護,記得不要重名哦!


3、配置xcode11 如圖點擊 capability



![]()
二、代碼處理(OC,需要swift的看最后的參考鏈接,不過邏輯都是一樣的)
1、導入系統頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設置ASAuthorizationAppleIDButton相關布局,並添加按鈕點擊響應事件
1.1 Sign In with Apple登錄按鈕有很多種樣式可以修改style查看
1.2 這里我使用的通知獲取蘋果返回的驗證信息(
viewDidLoad添加通知,dealloc移除通知
)
1.3 這里把獲取蘋果返回的驗證信息工具類做成單例(具體代碼見2)
注意: 蘋果給的驗證信息后台只能驗證一次,並且是5分鍾內,所以這里的登錄操作不要重復,不然下次的重復登錄會token驗證失敗
// sign in with apple // 使用系統提供的按鈕,要注意不支持系統版本的處理 if (@available(iOS 13.0, *)) { vOrLine.hidden = YES; // Sign In With Apple Button ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhiteOutline]; appleIDBtn.frame = CGRectMake(30, 5, v.width - 60, 40); [appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside]; [v addSubview:appleIDBtn]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(signInApple:) name:NOTIFICATION_SignInApple object:nil]; }
// 使用系統提供的按鈕調用處理授權的方法 - (void)didAppleIDBtnClicked{ // 封裝Sign In with Apple [[DhSignInApple shareInstance] handleAuthorizationAppleIDButtonPress]; } -(void)signInApple : (NSNotification *)notification{ NSDictionary *userInfo = notification.userInfo; // 獲取到數據 進行后台登錄 }
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
2、獲取授權碼並驗證
2.1這里我把昵稱也返給后台
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @class DhSignInApple; typedef void(^success) (NSDictionary *dic); typedef void(^failure) (NSString *errorMsg); @interface DhSignInApple : NSObject @property (nonatomic, copy)success successblock; // @property (nonatomic, copy)failure failureblock; // //實例化對象 + (instancetype)shareInstance; // 處理授權 - (void)handleAuthorizationAppleIDButtonPress; // 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶 - (void)perfomExistingAccountSetupFlows; @end NS_ASSUME_NONNULL_END
#import "DhSignInApple.h" #import"Helper.h" #import <AuthenticationServices/AuthenticationServices.h> @interface DhSignInApple ()<ASAuthorizationControllerDelegate,ASAuthorizationControllerPresentationContextProviding> @end @implementation DhSignInApple //實例化對象 + (instancetype)shareInstance { static DhSignInApple *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [DhSignInApple new]; }); return instance; } // 處理授權 - (void)handleAuthorizationAppleIDButtonPress{ if (@available(iOS 13.0, *)) { // 基於用戶的Apple ID授權用戶,生成用戶授權請求的一種機制 ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init]; // 創建新的AppleID 授權請求 ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest]; // 在用戶授權期間請求的聯系信息 appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]; // 由ASAuthorizationAppleIDProvider創建的授權請求 管理授權請求的控制器 ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]]; // 設置授權控制器通知授權請求的成功與失敗的代理 authorizationController.delegate = self; // 設置提供 展示上下文的代理,在這個上下文中 系統可以展示授權界面給用戶 authorizationController.presentationContextProvider = self; // 在控制器初始化期間啟動授權流 [authorizationController performRequests]; }else{ // 處理不支持系統版本 NSLog(@"該系統版本不可用Apple登錄"); } } // 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶 - (void)perfomExistingAccountSetupFlows{ NSLog(@"///已經認證過了/////"); if (@available(iOS 13.0, *)) { // 基於用戶的Apple ID授權用戶,生成用戶授權請求的一種機制 ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init]; // 授權請求AppleID ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest]; // 為了執行鑰匙串憑證分享生成請求的一種機制 ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init]; ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest]; // 由ASAuthorizationAppleIDProvider創建的授權請求 管理授權請求的控制器 ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]]; // 設置授權控制器通知授權請求的成功與失敗的代理 authorizationController.delegate = self; // 設置提供 展示上下文的代理,在這個上下文中 系統可以展示授權界面給用戶 authorizationController.presentationContextProvider = self; // 在控制器初始化期間啟動授權流 [authorizationController performRequests]; }else{ // 處理不支持系統版本 NSLog(@"該系統版本不可用Apple登錄"); } } #pragma mark - delegate //@optional 授權成功地回調 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){ NSLog(@"授權完成:::%@", authorization.credential); NSLog(@"%s", __FUNCTION__); NSLog(@"%@", controller); NSLog(@"%@", authorization); if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { // 用戶登錄使用ASAuthorizationAppleIDCredential ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential; NSString *user = appleIDCredential.user; // 使用過授權的,可能獲取不到以下三個參數 NSString *familyName = appleIDCredential.fullName.familyName; NSString *givenName = appleIDCredential.fullName.givenName; NSString *email = appleIDCredential.email; NSData *identityToken = appleIDCredential.identityToken; NSData *authorizationCode = appleIDCredential.authorizationCode; // 服務器驗證需要使用的參數 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. // 需要使用鑰匙串的方式保存用戶的唯一信息 // [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user]; // nick_name NSString *name; if (familyName) { name = familyName; } if (givenName) { if (name.length > 0) { name = [NSString stringWithFormat:@"%@%@",name,givenName]; }else{ name = givenName; } } if(user.length > 0){ dispatch_async(dispatch_get_main_queue(), ^{ if (name.length > 0) { [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SignInApple object:nil userInfo:@{@"ios_uid":user,@"nick_name":name,@"identity_token":identityTokenStr,@"authorization_code":authorizationCodeStr}]; }else{ [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SignInApple object:nil userInfo:@{@"ios_uid":user,@"identity_token":identityTokenStr,@"authorization_code":authorizationCodeStr}]; } }); } }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){ // 這個獲取的是iCloud記錄的賬號密碼,需要輸入框支持iOS 12 記錄賬號密碼的新特性,如果不支持,可以忽略 // Sign in using an existing iCloud Keychain credential. // 用戶登錄使用現有的密碼憑證 ASPasswordCredential *passwordCredential = authorization.credential; // 密碼憑證對象的用戶標識 用戶的唯一標識 NSString *user = passwordCredential.user; // 密碼憑證對象的密碼 NSString *password = passwordCredential.password; }else{ NSLog(@"授權信息均不符"); } } // 授權失敗的回調 - (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 = @"用戶取消了授權請求"; break; case ASAuthorizationErrorFailed: errorMsg = @"授權請求失敗"; break; case ASAuthorizationErrorInvalidResponse: errorMsg = @"授權請求響應無效"; break; case ASAuthorizationErrorNotHandled: errorMsg = @"未能處理授權請求"; break; case ASAuthorizationErrorUnknown: errorMsg = @"授權請求失敗未知原因"; break; default: break; } NSLog(@"%@", errorMsg); } // 告訴代理應該在哪個window 展示內容給用戶 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){ NSLog(@"---window"); // 返回window return [UIApplication sharedApplication].windows.lastObject; } @end
在授權登錄成功回調中,我們可以拿到以下幾類數據
UserID:Unique, stable, team-scoped user ID,蘋果用戶唯一標識符,該值在同一個開發者賬號下的所有App下是一樣的,開發者可以用該唯一標識符與自己后台系統的賬號體系綁定起來(這與國內的微信、QQ、微博等第三方登錄流程基本一致)
Verification data:Identity token, code,驗證數據,用於傳給開發者后台服務器,然后開發者服務器再向蘋果的身份驗證服務端驗證,本次授權登錄請求數據的有效性和真實性,詳見Sign In with Apple REST API
Account information:Name, verified email,蘋果用戶信息,包括全名、郵箱等,
注意:如果玩家登錄時拒絕提供真實的郵箱賬號,蘋果會生成虛擬的郵箱賬號,而且記錄過的蘋果賬號再次登錄這些參數拿不到 。使用過授權的,下次就獲取不到Name, email這些了。測試時可以進入設置密碼與安全性--使用您Apple ID的APP--進行編輯刪除操作,再次重新授權!
三、服務端
以上,從客戶端拿到user 、 identityToken 、 authorizationCode 等相關信息。那么怎么來校驗信息的正確性呢?
identityToken做個校驗,以某一次授權拿到的數據來舉個例子。在上文的授權回調中拿到的
identityToken來驗證,得到如下結果
identityToken = "eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmZjYm94LmhpdmVjb25zdW1lciIsImV4cCI6MTU3NjIxNjc3NSwiaWF0IjoxNTc2MjE2MTc1LCJzdWIiOiIwMDE4NTcuNDBhODZjNDM4MzMwNDczNDgzZjk1YzcyMDA3MzY2YTYuMTAxNSIsImNfaGFzaCI6IjNCQlc1bWlaR3ZEX3RzNkNZdlUwR0EiLCJlbWFpbCI6Im45cHZ2Zms2cnNAcHJpdmF0ZXJlbGF5LmFwcGxlaWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiaXNfcHJpdmF0ZV9lbWFpbCI6InRydWUiLCJhdXRoX3RpbWUiOjE1NzYyMTYxNzV9.Nksq3o1E8UxD4V7GmJB7ZrS0vSj_mm_ybdo7eiSbbAYNk6RnLuaRiJQYtI64mkZ-TqdeBgJmWt5bcSrW1gsWYk85YGeK79cIHaYO7nRIX1-e3_ociEJ3_dCECThrp-aMKZzq0yDz-xzbokZVsI4WmPcKlqhuE6ul2FBHwQrT3bTnxk_jB_4htqGjSW9u2cp2m-WbLrCgsorND3Z7w4KBICcEMqRnVbjTijO__-sgreXrFwDPu3LzccGQMr9cOugJorEe7gIEnACfOSF40YrsZ344SZfZ0VK9O8zOp6BoWw3yORDQiHkRjS0V9Tmi5SHQCGZ17kbjlrPUOQA0HgsVTQ"
https://appleid.apple.com/auth/token 該接口,
client_id:
app的
bundle identifier
client_secret: 需要我們自己生成,下文講解生成方法
code: 即為手機端獲取到的
authorizationCode 信息
grant_type: 固定字符串
authorization_code
拿到上面4個參數之后,發起請求
client_secret。下文代碼為
Ruby 代碼,確保已安裝
ruby環境。創建一個
secret_gen.rb文件,把下面的代碼拷貝進去。執行
ruby secret_gen.rb即可生成
client_secret
require "jwt" key_file = "客戶端步驟2中生成的私鑰.p8文件的路徑" team_id = "Team ID" client_id = "Bundle ID" key_id = "Key ID" 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

其他編程語言生成client_secret代碼,參考這個吧,或許有用
https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
https://www.jianshu.com/p/2342260c95ff
team_id 如何獲取 登錄后在這里看
https://developer.apple.com/account/#/membership/
4、把生成的client_secret代入步驟2中,得到的參數解釋看這個文檔,拿到id_token其也是一個JWT數據,回到JWT官網decode 出 payload 部分
比對服務端步驟1和步驟4圖中的
aud與
sub是否一致,若信息一致確定成功登錄,其中
aud為
app的
bundleID。由於沒有涉及到網頁登錄所以並沒有集成
iCloud KeyChain password
以上就是關於
Sign in with Apple 的相關內容和集成方法。
參考:
https://www.jianshu.com/p/e1284bd8c72a
https://www.jianshu.com/p/4523c72c50bd
