iOS 13-Sign In with Apple

最近了解了iOS 13新增功能之Sign In with Apple妄讯,Sign In with Apple是跨平臺的,可以支持iOS员帮、macOS、watchOS导饲、tvOS捞高、JS。本文主要內(nèi)容為Sign In with AppleiOS上的基礎(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,或者也可以不寫
}
獲取key_id
  • 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 AppleXcode的準(zhǔn)備工作
    Xcode11 Signing & Capabilities中添加Sign In With Apple睡腿,如下圖

  • iOS Sign In with Apple流程

  1. 導(dǎo)入系統(tǒng)頭文件#import <AuthenticationServices/AuthenticationServices.h>语御,添加Sign In with Apple登錄按鈕,設(shè)置ASAuthorizationAppleIDButton相關(guān)布局席怪,并添加按鈕點擊響應(yīng)事件
  2. 獲取授權(quán)碼
  3. 驗證
  1. 導(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憑證宝鼓,提示用戶直接使用TouchIDFaceID登錄即可刑棵,代碼如下
// 如果存在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登錄");
    }
}
  1. 獲取授權(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ù)拿不到
  1. 驗證
    關(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_tokenJWT數(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與你appbundleID一致迅诬,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
附:我的博客地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市等脂,隨后出現(xiàn)的幾起案子俏蛮,更是在濱河造成了極大的恐慌,老刑警劉巖上遥,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搏屑,死亡現(xiàn)場離奇詭異,居然都是意外死亡粉楚,警方通過查閱死者的電腦和手機辣恋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來模软,“玉大人伟骨,你說我怎么就攤上這事∪家欤” “怎么了携狭?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長特铝。 經(jīng)常有香客問我暑中,道長,這世上最難降的妖魔是什么鲫剿? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任鳄逾,我火速辦了婚禮,結(jié)果婚禮上灵莲,老公的妹妹穿的比我還像新娘雕凹。我一直安慰自己,他們只是感情好政冻,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布枚抵。 她就那樣靜靜地躺著,像睡著了一般明场。 火紅的嫁衣襯著肌膚如雪汽摹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天苦锨,我揣著相機與錄音逼泣,去河邊找鬼趴泌。 笑死,一個胖子當(dāng)著我的面吹牛拉庶,可吹牛的內(nèi)容都是我干的嗜憔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼氏仗,長吁一口氣:“原來是場噩夢啊……” “哼吉捶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起皆尔,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤呐舔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后床佳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滋早,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡榄审,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年砌们,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搁进。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浪感,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饼问,到底是詐尸還是另有隱情影兽,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布莱革,位于F島的核電站峻堰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盅视。R本人自食惡果不足惜捐名,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闹击。 院中可真熱鬧镶蹋,春花似錦、人聲如沸赏半。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽断箫。三九已至拂酣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仲义,已是汗流浹背婶熬。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工丹莲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尸诽。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓甥材,卻偏偏與公主長得像,于是被迫代替她去往敵國和親性含。 傳聞我的和親對象是個殘疾皇子洲赵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容