第一步:導(dǎo)入庫文件
#import <AuthenticationServices/AuthenticationServices.h>
第二步:繪制登錄按鈕,也可以自定義
-(void)setupUI{
if (@available(iOS 13.0, *)) {
// Sign In With Apple Button
ASAuthorizationAppleIDButton *appleIDButton = [ASAuthorizationAppleIDButton new];
appleIDButton.frame = CGRectMake(.0, .0, CGRectGetWidth(self.view.frame) - 40.0, 100.0);
CGPoint origin = CGPointMake(20.0, CGRectGetMidY(self.view.frame));
CGRect frame = appleIDButton.frame;
frame.origin = origin;
appleIDButton.frame = frame;
appleIDButton.cornerRadius = CGRectGetHeight(appleIDButton.frame) * 0.25;
[self.view addSubview:appleIDButton];
[appleIDButton addTarget:self action:@selector(handleAuthrization:) forControlEvents:UIControlEventTouchUpInside];
}
}
第三步:發(fā)起授權(quán)
//! 處理授權(quán)
- (void)handleAuthrization:(UIButton *)sender {
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID授權(quán)用戶,生成用戶授權(quán)請(qǐng)求的一種機(jī)制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 創(chuàng)建新的AppleID 授權(quán)請(qǐng)求
ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
// 在用戶授權(quán)期間請(qǐng)求的聯(lián)系信息
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
/*
// 為了執(zhí)行鑰匙串憑證分享生成請(qǐng)求的一種機(jī)制,暫時(shí)好像沒啥用
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
*/
// 由ASAuthorizationAppleIDProvider創(chuàng)建的授權(quán)請(qǐng)求 管理授權(quán)請(qǐng)求的控制器
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
// 設(shè)置授權(quán)控制器通知授權(quán)請(qǐng)求的成功與失敗的代理
controller.delegate = self;
// 設(shè)置提供 展示上下文的代理,在這個(gè)上下文中 系統(tǒng)可以展示授權(quán)界面給用戶
controller.presentationContextProvider = self;
// 在控制器初始化期間啟動(dòng)授權(quán)流
[controller performRequests];
}
}
第四步:授權(quán)結(jié)果處理
#pragma mark - Delegate
//! 授權(quán)成功地回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
NSLog(@"authorization.credential:%@", authorization.credential);
NSMutableString *mStr = [NSMutableString string];
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用戶登錄使用ASAuthorizationAppleIDCredential
ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
///憑證信息
//用戶唯一ID
NSString *user = appleIDCredential.user;
//授權(quán)驗(yàn)證信息
NSData *token = appleIDCredential.identityToken;
//用戶名,郵箱信息(只有第一次授權(quán)才會(huì)帶回來,以后授權(quán)成功都是返回空)
NSString *familyName = appleIDCredential.fullName.familyName;
NSString *givenName = appleIDCredential.fullName.givenName;
NSString *email = appleIDCredential.email;
} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
// 這個(gè)獲取的是iCloud記錄的賬號(hào)密碼,需要輸入框支持iOS 12 記錄賬號(hào)密碼的新特性
//這個(gè)回調(diào)我是沒有測(cè)試出來過,不知道怎么搞
// 用戶登錄使用現(xiàn)有的密碼憑證
ASPasswordCredential *passwordCredential = authorization.credential;
// 密碼憑證對(duì)象的用戶標(biāo)識(shí) 用戶的唯一標(biāo)識(shí)
NSString *user = passwordCredential.user;
// 注意 存儲(chǔ)用戶標(biāo)識(shí)信息需要使用鑰匙串來存儲(chǔ) 這里筆者簡(jiǎn)單期間 使用NSUserDefaults 做的簡(jiǎn)單示例
[[NSUserDefaults standardUserDefaults] setObject:user forKey:@"userIdentifier"];
// 密碼憑證對(duì)象的密碼
NSString *password = passwordCredential.password;
} else {
NSLog(@"授權(quán)信息均不符");
}
}
//! 授權(quán)失敗的回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
NSLog(@"%s", __FUNCTION__);
NSLog(@"錯(cuò)誤信息:%@", 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)無效";
break;
case ASAuthorizationErrorNotHandled:
errorMsg = @"未能處理授權(quán)請(qǐng)求";
break;
case ASAuthorizationErrorUnknown:
errorMsg = @"授權(quán)請(qǐng)求失敗未知原因";
break;
}
NSLog(@"error:%@", errorMsg);
}
//告訴代理應(yīng)該在哪個(gè)window 展示內(nèi)容給用戶
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
NSLog(@"調(diào)用展示window方法:%s", __FUNCTION__);
// 返回window
return self.view.window;
}
在授權(quán)登錄成功回調(diào)中,我們可以拿到以下幾類數(shù)據(jù)
-
UserID:
Unique, stable, team-scoped user ID,蘋果用戶唯一標(biāo)識(shí)符,該值在同一個(gè)開發(fā)者賬號(hào)下的所有App下是一樣的,開發(fā)者可以用該唯一標(biāo)識(shí)符與自己后臺(tái)系統(tǒng)的賬號(hào)體系綁定起來(這與國內(nèi)的微信、QQ、微博等第三方登錄流程基本一致) -
Verification data:
Identity token, code,驗(yàn)證數(shù)據(jù),用于傳給開發(fā)者后臺(tái)服務(wù)器,然后開發(fā)者服務(wù)器再向蘋果的身份驗(yàn)證服務(wù)端驗(yàn)證,本次授權(quán)登錄請(qǐng)求數(shù)據(jù)的有效性和真實(shí)性,詳見Sign In with Apple REST API -
Account information:
Name, verified email,蘋果用戶信息,包括全名、郵箱等,注意:如果玩家登錄時(shí)拒絕提供真實(shí)的郵箱賬號(hào),蘋果會(huì)生成虛擬的郵箱賬號(hào),而且記錄過的蘋果賬號(hào)再次登錄這些參數(shù)拿不到
驗(yàn)證
關(guān)于驗(yàn)證的這一步,需要傳遞授權(quán)碼給自己的服務(wù)端,自己的服務(wù)端調(diào)用蘋果API去校驗(yàn)授權(quán)碼Generate and validate tokens。如果驗(yàn)證成功,可以根據(jù)userIdentifier判斷賬號(hào)是否已存在,若存在,則返回自己賬號(hào)系統(tǒng)的登錄態(tài),若不存在,則創(chuàng)建一個(gè)新的賬號(hào),并返回對(duì)應(yīng)的登錄狀態(tài)給App
- 推薦驗(yàn)證步驟為:
- 服務(wù)端拿
authorizationCode去蘋果后臺(tái)驗(yàn)證,驗(yàn)證地址https://appleid.apple.com/auth/token,蘋果返回id_token,與客戶端獲取的identityToken值一樣,格式如下
{
"access_token": "一個(gè)token",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "一個(gè)token",
"id_token": "結(jié)果是JWT,字符串形式,identityToken"
}
另外授權(quán)code是有時(shí)效性的,且使用一次即失效
- 服務(wù)器拿到相應(yīng)結(jié)果后,其中
id_token是JWT數(shù)據(jù),解碼id_token,得到如下內(nèi)容
{
"iss":"https://appleid.apple.com",
"aud":"這個(gè)是你的app的bundle identifier",
"exp":1567482337,
"iat":1567481737,
"sub":"這個(gè)字段和客戶端獲取的user字段是完全一樣的",
"c_hash":"8KDzfalU5kygg5zxXiX7dA",
"auth_time":1567481737
}
其中aud與你app的bundleID一致,sub就是授權(quán)用戶的唯一標(biāo)識(shí),與手機(jī)端獲得的user一致,服務(wù)器端通過對(duì)比sub字段信息是否與手機(jī)端上傳的user信息一致來確定是否成功登錄
該token的有效期是10分鐘,具體后端驗(yàn)證參考附錄
第五步:已授權(quán)狀態(tài)獲取與監(jiān)聽
1.獲取已授權(quán)用戶當(dāng)前狀態(tài)
- (void)authorizationCredentialState{
if (@available(iOS 13.0, *)) {
// 基于用戶的Apple ID 生成授權(quán)用戶請(qǐng)求的機(jī)制
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
// 注意 存儲(chǔ)用戶標(biāo)識(shí)信息需要使用鑰匙串來存儲(chǔ) 這里筆者簡(jiǎn)單期間 使用NSUserDefaults 做的簡(jiǎn)單示例
NSString *userIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"userIdentifier"];
if (userIdentifier) {
__block NSString *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) {
// 蘋果證書的授權(quán)狀態(tài)
case ASAuthorizationAppleIDProviderCredentialRevoked:
// 蘋果授權(quán)憑證失效,退出登錄,重新授權(quán)
errorMsg = @"蘋果授權(quán)憑證失效";
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
// 蘋果授權(quán)憑證狀態(tài)良好
errorMsg = @"蘋果授權(quán)憑證狀態(tài)良好";
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
// 未發(fā)現(xiàn)蘋果授權(quán)憑證,退出登錄,重新授權(quán)
errorMsg = @"未發(fā)現(xiàn)蘋果授權(quán)憑證";
break;
// 可以引導(dǎo)用戶重新登錄
case ASAuthorizationAppleIDProviderCredentialTransferred:
errorMsg = @"蘋果授權(quán)信息變動(dòng)";
break;
}
}];
}
}
}
2.使用通知的方式監(jiān)聽授權(quán)狀態(tài)變化
//! 添加蘋果登錄的狀態(tài)通知
- (void)observeAppleSignInState {
if (@available(iOS 13.0, *)) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
}
//! 觀察SignInWithApple狀態(tài)改變
- (void)handleSignInWithAppleStateChanged:(id)noti {
NSLog(@"%s", __FUNCTION__);
NSLog(@"%@", noti);
}
- (void)dealloc {
if (@available(iOS 13.0, *)) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
}
參考:http://www.itdecent.cn/p/e1284bd8c72a
附:官方示例代碼 Swift 版
附:What the Heck is Sign In with Apple?
附:Sign In with Apple 從登陸到服務(wù)器驗(yàn)證
附:蘋果授權(quán)登陸后端驗(yàn)證
附:[官方文檔] Generate and validate tokens
附:[官方文檔] App Store審核指南
附:SignInAppleDemo