最近了解了iOS 13
新增功能之Sign In with Apple
妄讯,Sign In with Apple
是跨平臺的,可以支持iOS员帮、macOS、watchOS导饲、tvOS捞高、JS
。本文主要內(nèi)容為Sign In with Apple
在iOS
上的基礎(chǔ)使用渣锦。詳情參考WWDC 2019
- 審核備注
New Guidelines for Sign in with Apple
We’ve updated the App Store Review Guidelines to provide criteria for when apps are required to use Sign in with Apple. Starting today, new apps submitted to the App Store must follow these guidelines. Existing apps and app updates must follow them by April 2020. We’ve also provided new guidelines for using Sign in with Apple on the web and other platforms.
September 12, 2019
也就是說硝岗,所有已接入其它第三方登錄的 App,Sign In with Apple 將被要求作為一種登錄選擇袋毙,否則就不給過型檀。從今天開始(2019-9-12),提交到App Store的新應(yīng)用必須遵循這些準(zhǔn)則听盖,現(xiàn)有應(yīng)用程序和應(yīng)用程序更新必須在2020年4月之前進行胀溺。詳情參考App Store審核指南
-
開發(fā)
Sign In with Apple
的注意事項
需要在蘋果后臺打開該選項,并且重新生成Profiles
配置文件皆看,并安裝到Xcode
仓坞,如下圖
服務(wù)端驗證需要的文件,一個是私鑰文件腰吟,一個是
config.json
文件-
創(chuàng)建用于客戶端身份驗證的私鑰
返回Certificates, Identifiers & Profiles
主屏幕无埃,從側(cè)面導(dǎo)航中選擇Keys
單擊Configure
按鈕,然后選擇你先前創(chuàng)建的Primary App ID
毛雇,保存之后录语,Apple
將為你生成一個新的私鑰,并讓你僅下載一次禾乘,請確保你保存了此文件澎埠,因為以后你將無法再次將其取回!
你下載的文件將以.p8
結(jié)尾始藕,可以將其重命名為key.txt
以便在后續(xù)步驟中更輕松地使用
- 創(chuàng)建
config.json
新文件蒲稳,格式、內(nèi)容和參數(shù)說明如下
{
"client_id": "實際上被稱為“Service ID”伍派,您將在“Identifiers”部分創(chuàng)建它江耀,其實就是應(yīng)用的bundleID",
"team_id": "后臺賬號的teamID",
"redirect_uri": "重定向url,網(wǎng)頁登錄需要诉植,只是客服端登錄可以不寫",
"key_id": "在蘋果后臺獲取祥国,如下圖",
"scope": "設(shè)置我們要從用戶那里收集什么信息,我們可以設(shè)置email和name,或者也可以不寫
}
-
web
使用Sign In with Apple
的相關(guān)配置舌稀,不需要web登錄的啊犬,以下配置可以忽略
- 創(chuàng)建
Services ID
在下一步中,你將定義用戶在登錄流程中將看到的應(yīng)用程序的名稱壁查,并定義成為OAuth
的標(biāo)識符client_id
觉至,確保還選中Sign In with Apple
復(fù)選框
- 創(chuàng)建
web Authentication Configuration
,定義應(yīng)用程序的重定向URL
-
iOS
使用Sign In with Apple
在Xcode
的準(zhǔn)備工作
在Xcode11 Signing & Capabilities
中添加Sign In With Apple
睡腿,如下圖
iOS Sign In with Apple
流程
- 導(dǎo)入系統(tǒng)頭文件#import <AuthenticationServices/AuthenticationServices.h>语御,添加Sign In with Apple登錄按鈕,設(shè)置ASAuthorizationAppleIDButton相關(guān)布局席怪,并添加按鈕點擊響應(yīng)事件
- 獲取授權(quán)碼
- 驗證
- 導(dǎo)入系統(tǒng)頭文件
#import <AuthenticationServices/AuthenticationServices.h>
应闯,添加Sign In with Apple
登錄按鈕,設(shè)置ASAuthorizationAppleIDButton
相關(guān)布局挂捻,并添加按鈕點擊響應(yīng)事件孽锥。當(dāng)然蘋果也允許自定義蘋果登錄按鈕的樣式,樣式要求詳見這個文檔:Human Interface Guidelines
- (void)configUI{
// 使用系統(tǒng)提供的按鈕细层,要注意不支持系統(tǒng)版本的處理
if (@available(iOS 13.0, *)) {
// Sign In With Apple Button
ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180, self.view.bounds.size.width - 60, 100);
// appleBtn.cornerRadius = 22.f;
[appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:appleIDBtn];
}
// 或者自己用UIButton實現(xiàn)按鈕樣式
UIButton *addBtn = [UIButton buttonWithType:UIButtonTypeCustom];
addBtn.frame = CGRectMake(30, 80, self.view.bounds.size.width - 60, 44);
addBtn.backgroundColor = [UIColor orangeColor];
[addBtn setTitle:@"Sign in with Apple" forState:UIControlStateNormal];
[addBtn addTarget:self action:@selector(didCustomBtnClicked) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:addBtn];
}
// 自己用UIButton按鈕調(diào)用處理授權(quán)的方法
- (void)didCustomBtnClicked{
// 封裝Sign In with Apple 登錄工具類惜辑,使用這個類時要把類對象設(shè)置為全局變量,或者直接把這個工具類做成單例疫赎,如果使用局部變量盛撑,和IAP支付工具類一樣,會導(dǎo)致蘋果回調(diào)不會執(zhí)行
self.signInApple = [[SignInApple alloc] init];
[self.signInApple handleAuthorizationAppleIDButtonPress];
}
// 使用系統(tǒng)提供的按鈕調(diào)用處理授權(quán)的方法
- (void)didAppleIDBtnClicked{
// 封裝Sign In with Apple 登錄工具類捧搞,使用這個類時要把類對象設(shè)置為全局變量抵卫,或者直接把這個工具類做成單例,如果使用局部變量胎撇,和IAP支付工具類一樣介粘,會導(dǎo)致蘋果回調(diào)不會執(zhí)行
self.signInApple = [[SignInApple alloc] init];
[self.signInApple handleAuthorizationAppleIDButtonPress];
}
// 處理授權(quán)
- (void)handleAuthorizationAppleIDButtonPress{
NSLog(@"http:////////");
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請求的一種機制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 創(chuàng)建新的AppleID 授權(quán)請求
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 在用戶授權(quán)期間請求的聯(lián)系信息
appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
// 由ASAuthorizationAppleIDProvider創(chuàng)建的授權(quán)請求 管理授權(quán)請求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
// 設(shè)置授權(quán)控制器通知授權(quán)請求的成功與失敗的代理
authorizationController.delegate = self;
// 設(shè)置提供 展示上下文的代理晚树,在這個上下文中 系統(tǒng)可以展示授權(quán)界面給用戶
authorizationController.presentationContextProvider = self;
// 在控制器初始化期間啟動授權(quán)流
[authorizationController performRequests];
}else{
// 處理不支持系統(tǒng)版本
NSLog(@"該系統(tǒng)版本不可用Apple登錄");
}
}
- 注意:封裝Sign In with Apple 登錄工具類姻采,使用這個類時要把類對象設(shè)置為全局變量,或者直接把這個工具類做成單例爵憎,如果使用局部變量慨亲,和IAP支付工具類一樣,會導(dǎo)致蘋果回調(diào)不會執(zhí)行
- 已經(jīng)使用
Sign In with Apple
登錄過app
的用戶
如果設(shè)備中存在iCloud Keychain
憑證或者AppleID
憑證宝鼓,提示用戶直接使用TouchID
或FaceID
登錄即可刑棵,代碼如下
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows{
NSLog(@"http:///已經(jīng)認證過了/////");
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請求的一種機制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 授權(quán)請求AppleID
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 為了執(zhí)行鑰匙串憑證分享生成請求的一種機制
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
// 由ASAuthorizationAppleIDProvider創(chuàng)建的授權(quán)請求 管理授權(quán)請求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
// 設(shè)置授權(quán)控制器通知授權(quán)請求的成功與失敗的代理
authorizationController.delegate = self;
// 設(shè)置提供 展示上下文的代理愚铡,在這個上下文中 系統(tǒng)可以展示授權(quán)界面給用戶
authorizationController.presentationContextProvider = self;
// 在控制器初始化期間啟動授權(quán)流
[authorizationController performRequests];
}else{
// 處理不支持系統(tǒng)版本
NSLog(@"該系統(tǒng)版本不可用Apple登錄");
}
}
- 獲取授權(quán)碼
獲取授權(quán)碼需要在代碼中實現(xiàn)兩個代理回調(diào)ASAuthorizationControllerDelegate蛉签、ASAuthorizationControllerPresentationContextProviding
分別用于處理授權(quán)登錄成功和失敗、以及提供用于展示授權(quán)頁面的Window
,代碼如下
#pragma mark - delegate
//@optional 授權(quán)成功地回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
NSLog(@"授權(quán)完成:::%@", authorization.credential);
NSLog(@"%s", __FUNCTION__);
NSLog(@"%@", controller);
NSLog(@"%@", authorization);
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用戶登錄使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
NSString *user = appleIDCredential.user;
// 使用過授權(quán)的碍舍,可能獲取不到以下三個參數(shù)
NSString *familyName = appleIDCredential.fullName.familyName;
NSString *givenName = appleIDCredential.fullName.givenName;
NSString *email = appleIDCredential.email;
NSData *identityToken = appleIDCredential.identityToken;
NSData *authorizationCode = appleIDCredential.authorizationCode;
// 服務(wù)器驗證需要使用的參數(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.
// 需要使用鑰匙串的方式保存用戶的唯一信息
// [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];
}else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
// 這個獲取的是iCloud記錄的賬號密碼柠座,需要輸入框支持iOS 12 記錄賬號密碼的新特性,如果不支持乒验,可以忽略
// Sign in using an existing iCloud Keychain credential.
// 用戶登錄使用現(xiàn)有的密碼憑證
ASPasswordCredential *passwordCredential = authorization.credential;
// 密碼憑證對象的用戶標(biāo)識 用戶的唯一標(biāo)識
NSString *user = passwordCredential.user;
// 密碼憑證對象的密碼
NSString *password = passwordCredential.password;
}else{
NSLog(@"授權(quán)信息均不符");
}
}
// 授權(quán)失敗的回調(diào)
- (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)請求";
break;
case ASAuthorizationErrorFailed:
errorMsg = @"授權(quán)請求失敗";
break;
case ASAuthorizationErrorInvalidResponse:
errorMsg = @"授權(quán)請求響應(yīng)無效";
break;
case ASAuthorizationErrorNotHandled:
errorMsg = @"未能處理授權(quán)請求";
break;
case ASAuthorizationErrorUnknown:
errorMsg = @"授權(quán)請求失敗未知原因";
break;
default:
break;
}
NSLog(@"%@", errorMsg);
}
// 告訴代理應(yīng)該在哪個window 展示內(nèi)容給用戶
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
NSLog(@"88888888888");
// 返回window
return [UIApplication sharedApplication].windows.lastObject;
}
在授權(quán)登錄成功回調(diào)中愚隧,我們可以拿到以下幾類數(shù)據(jù)
-
UserID:
Unique, stable, team-scoped user ID
蒂阱,蘋果用戶唯一標(biāo)識符锻全,該值在同一個開發(fā)者賬號下的所有App
下是一樣的,開發(fā)者可以用該唯一標(biāo)識符與自己后臺系統(tǒng)的賬號體系綁定起來(這與國內(nèi)的微信录煤、QQ鳄厌、微博
等第三方登錄流程基本一致) -
Verification data:
Identity token, code
,驗證數(shù)據(jù)妈踊,用于傳給開發(fā)者后臺服務(wù)器了嚎,然后開發(fā)者服務(wù)器再向蘋果的身份驗證服務(wù)端驗證,本次授權(quán)登錄請求數(shù)據(jù)的有效性和真實性腥泥,詳見Sign In with Apple REST API -
Account information:
Name, verified email
杏头,蘋果用戶信息苍狰,包括全名、郵箱等呐伞,注意:如果玩家登錄時拒絕提供真實的郵箱賬號,蘋果會生成虛擬的郵箱賬號慎式,而且記錄過的蘋果賬號再次登錄這些參數(shù)拿不到
- 驗證
關(guān)于驗證的這一步伶氢,需要傳遞授權(quán)碼給自己的服務(wù)端,自己的服務(wù)端調(diào)用蘋果API
去校驗授權(quán)碼Generate and validate tokens瘪吏。如果驗證成功癣防,可以根據(jù)userIdentifier
判斷賬號是否已存在,若存在掌眠,則返回自己賬號系統(tǒng)的登錄態(tài)蕾盯,若不存在,則創(chuàng)建一個新的賬號蓝丙,并返回對應(yīng)的登錄狀態(tài)給App
- 推薦驗證步驟為:
- 服務(wù)端拿
authorizationCode
去蘋果后臺驗證刑枝,驗證地址https://appleid.apple.com/auth/token
,蘋果返回id_token
迅腔,與客戶端獲取的identityToken
值一樣装畅,格式如下
{
"access_token": "一個token",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "一個token",
"id_token": "結(jié)果是JWT,字符串形式沧烈,identityToken"
}
另外授權(quán)code
是有時效性的掠兄,且使用一次即失效
- 服務(wù)器拿到相應(yīng)結(jié)果后,其中
id_token
是JWT
數(shù)據(jù),解碼id_token
蚂夕,得到如下內(nèi)容
{
"iss":"https://appleid.apple.com",
"aud":"這個是你的app的bundle identifier",
"exp":1567482337,
"iat":1567481737,
"sub":"這個字段和客戶端獲取的user字段是完全一樣的",
"c_hash":"8KDzfalU5kygg5zxXiX7dA",
"auth_time":1567481737
}
其中aud
與你app
的bundleID
一致迅诬,sub
就是授權(quán)用戶的唯一標(biāo)識,與手機端獲得的user
一致婿牍,服務(wù)器端通過對比sub
字段信息是否與手機端上傳的user
信息一致來確定是否成功登錄
該token
的有效期是10
分鐘侈贷,具體后端驗證參考附錄
附:官方示例代碼 Swift 版
附:What the Heck is Sign In with Apple?
附:Sign In with Apple 從登陸到服務(wù)器驗證
附:蘋果授權(quán)登陸后端驗證
附:[官方文檔] Generate and validate tokens
附:[官方文檔] App Store審核指南
附:SignInAppleDemo
附:我的博客地址