iOS 13-Sign In with Apple

原文

iOS 13-Sign In with Apple

http://www.itdecent.cn/p/e1284bd8c72a

最近了解了iOS 13新增功能之Sign In with Apple,Sign In with Apple是跨平臺(tái)的,可以支持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
也就是說(shuō),所有已接入其它第三方登錄的 App,Sign In with Apple 將被要求作為一種登錄選擇,否則就不給過(guò)。從今天開始(2019-9-12),提交到App Store的新應(yīng)用必須遵循這些準(zhǔn)則,現(xiàn)有應(yīng)用程序和應(yīng)用程序更新必須在2020年4月之前進(jìn)行。詳情參考App Store審核指南

image
  • 開發(fā)Sign In with Apple的注意事項(xiàng)
    需要在蘋果后臺(tái)打開該選項(xiàng),并且重新生成Profiles配置文件,并安裝到Xcode,如下圖

    image
  • 服務(wù)端驗(yàn)證需要的文件,一個(gè)是私鑰文件,一個(gè)是config.json文件

  • 創(chuàng)建用于客戶端身份驗(yàn)證的私鑰
    返回Certificates, Identifiers & Profiles主屏幕,從側(cè)面導(dǎo)航中選擇Keys

    image

單擊Configure按鈕,然后選擇你先前創(chuàng)建的Primary App ID,保存之后,Apple將為你生成一個(gè)新的私鑰,并讓你僅下載一次,請(qǐng)確保你保存了此文件,因?yàn)橐院竽銓o(wú)法再次將其取回!你下載的文件將以.p8結(jié)尾,可以將其重命名為key.txt以便在后續(xù)步驟中更輕松地使用

  • 創(chuàng)建config.json新文件,格式、內(nèi)容和參數(shù)說(shuō)明如下
{
    "client_id": "實(shí)際上被稱為“Service ID”,您將在“Identifiers”部分創(chuàng)建它,其實(shí)就是應(yīng)用的bundleID”,
    "team_id": "后臺(tái)賬號(hào)的teamID”,
    "redirect_uri": "重定向url,網(wǎng)頁(yè)登錄需要,只是客服端登錄可以不寫”,
    "key_id": "在蘋果后臺(tái)獲取,如下圖”,
    "scope": "設(shè)置我們要從用戶那里收集什么信息,我們可以設(shè)置email和name,或者也可以不寫
}

image
  • web使用Sign In with Apple的相關(guān)配置,不需要web登錄的,以下配置可以忽略

  • 創(chuàng)建Services ID

    image

在下一步中,你將定義用戶在登錄流程中將看到的應(yīng)用程序的名稱,并定義成為OAuth的標(biāo)識(shí)符client_id,確保還選中Sign In with Apple復(fù)選框

image
  • 創(chuàng)建web Authentication Configuration,定義應(yīng)用程序的重定向URL
image
image
  • iOS使用Sign In with AppleXcode的準(zhǔn)備工作
    Xcode11 Signing & Capabilities中添加Sign In With Apple,如下圖

    image
  • iOS Sign In with Apple流程

  1. 導(dǎo)入系統(tǒng)頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設(shè)置ASAuthorizationAppleIDButton相關(guān)布局,并添加按鈕點(diǎn)擊響應(yīng)事件
  2. 獲取授權(quán)碼
  3. 驗(yàn)證
  1. 導(dǎo)入系統(tǒng)頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設(shè)置ASAuthorizationAppleIDButton相關(guān)布局,并添加按鈕點(diǎn)擊響應(yīng)事件。當(dāng)然蘋果也允許自定義蘋果登錄按鈕的樣式,樣式要求詳見(jiàn)這個(gè)文檔: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實(shí)現(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 登錄工具類,使用這個(gè)類時(shí)要把類對(duì)象設(shè)置為全局變量,或者直接把這個(gè)工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會(huì)導(dǎo)致蘋果回調(diào)不會(huì)執(zhí)行
    self.signInApple = [[SignInApple alloc] init];
    [self.signInApple handleAuthorizationAppleIDButtonPress];
}

// 使用系統(tǒng)提供的按鈕調(diào)用處理授權(quán)的方法
- (void)didAppleIDBtnClicked{
    // 封裝Sign In with Apple 登錄工具類,使用這個(gè)類時(shí)要把類對(duì)象設(shè)置為全局變量,或者直接把這個(gè)工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會(huì)導(dǎo)致蘋果回調(diào)不會(huì)執(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)請(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;
        // 在控制器初始化期間啟動(dòng)授權(quán)流
        [authorizationController performRequests];
    }else{
        // 處理不支持系統(tǒng)版本
        NSLog(@"該系統(tǒng)版本不可用Apple登錄”);
    }
}

  • 注意:封裝Sign In with Apple 登錄工具類,使用這個(gè)類時(shí)要把類對(duì)象設(shè)置為全局變量,或者直接把這個(gè)工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會(huì)導(dǎo)致蘋果回調(diào)不會(huì)執(zhí)行
  • 已經(jīng)使用Sign In with Apple登錄過(guò)app的用戶
    如果設(shè)備中存在iCloud Keychain憑證或者AppleID憑證,提示用戶直接使用TouchIDFaceID登錄即可,代碼如下
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows{
    NSLog(@"http:///已經(jīng)認(rèn)證過(guò)了/////“);

    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登錄”);
    }
}

  1. 獲取授權(quán)碼
    獲取授權(quán)碼需要在代碼中實(shí)現(xiàn)兩個(gè)代理回調(diào)ASAuthorizationControllerDelegate、ASAuthorizationControllerPresentationContextProviding分別用于處理授權(quán)登錄成功和失敗、以及提供用于展示授權(quán)頁(yè)面的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;
        // 使用過(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.
        //  需要使用鑰匙串的方式保存用戶的唯一信息
//        [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];

    }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
        // 這個(gè)獲取的是iCloud記錄的賬號(hào)密碼,需要輸入框支持iOS 12 記錄賬號(hào)密碼的新特性,如果不支持,可以忽略
        // Sign in using an existing iCloud Keychain credential.
        // 用戶登錄使用現(xiàn)有的密碼憑證
        ASPasswordCredential *passwordCredential = authorization.credential;
        // 密碼憑證對(duì)象的用戶標(biāo)識(shí) 用戶的唯一標(biāo)識(shí)
        NSString *user = passwordCredential.user;
        // 密碼憑證對(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)){
    // 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;
    }

    NSLog(@"%@", errorMsg);
}

// 告訴代理應(yīng)該在哪個(gè)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)識(shí)符,該值在同一個(gè)開發(fā)者賬號(hào)下的所有App下是一樣的,開發(fā)者可以用該唯一標(biāo)識(shí)符與自己后臺(tái)系統(tǒng)的賬號(hào)體系綁定起來(lái)(這與國(guó)內(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í)性,詳見(jiàn)Sign In with Apple REST API
  • Account information:Name, verified email,蘋果用戶信息,包括全名、郵箱等,注意:如果玩家登錄時(shí)拒絕提供真實(shí)的郵箱賬號(hào),蘋果會(huì)生成虛擬的郵箱賬號(hào),而且記錄過(guò)的蘋果賬號(hào)再次登錄這些參數(shù)拿不到
  1. 驗(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_tokenJWT數(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與你appbundleID一致,sub就是授權(quán)用戶的唯一標(biāo)識(shí),與手機(jī)端獲得的user一致,服務(wù)器端通過(guò)對(duì)比sub字段信息是否與手機(jī)端上傳的user信息一致來(lái)確定是否成功登錄
token的有效期是10分鐘,具體后端驗(yàn)證參考附錄

附:官方示例代碼 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
附:我的博客地址

</article>

132人點(diǎn)贊

iOS開發(fā)筆記-2019.下

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容