iOS 13-Sign In with Apple (iOS端) + (服務端)驗證

最近了解了iOS 13新增功能之Sign In with AppleSign In with Apple是跨平臺的,可以支持iOS、macOS、watchOS、tvOS、JS。本文主要內容為Sign In with AppleiOS上的基礎使用。詳情參考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的新應用必須遵循這些準則,現有應用程序和應用程序更新必須在2020年4月之前進行。詳情參考App Store審核指南

1948913-02e9ffa7532a8f7e.png

  • 開發Sign In with Apple的注意事項
    需要在蘋果后臺打開該選項,并且重新生成Profiles配置文件,并安裝到Xcode,如下圖


    1948913-ab1d0bdbb10e48ef.png
  • 服務端驗證需要的文件,一個是私鑰文件,一個是config.json文件

  • 創建用于客戶端身份驗證的私鑰
    返回Certificates, Identifiers & Profiles主屏幕,從側面導航中選擇Keys


    1948913-81dcd6151c88f6ad.png

    單擊Configure按鈕,然后選擇你先前創建的Primary App ID,保存之后,Apple將為你生成一個新的私鑰,并讓你僅下載一次,請確保你保存了此文件,因為以后你將無法再次將其取回!你下載的文件將以.p8結尾,可以將其重命名為key.txt以便在后續步驟中更輕松地使用

  • 創建config.json新文件,格式、內容和參數說明如下

{
    "client_id": "實際上被稱為“Service ID”,您將在“Identifiers”部分創建它,其實就是應用的bundleID",
    "team_id": "后臺賬號的teamID",
    "redirect_uri": "重定向url,網頁登錄需要,只是客服端登錄可以不寫",
    "key_id": "在蘋果后臺獲取,如下圖",
    "scope": "設置我們要從用戶那里收集什么信息,我們可以設置email和name,或者也可以不寫
}
1948913-58fb974833a5fb8f.png
  • web使用Sign In with Apple的相關配置,不需要web登錄的,以下配置可以忽略
  • 創建Services ID


    1948913-fd937773a15fb2bd.png

在下一步中,你將定義用戶在登錄流程中將看到的應用程序的名稱,并定義成為OAuth的標識符client_id,確保還選中Sign In with Apple復選框


1948913-ed8cfcc165ec0035.png
  • 創建web Authentication Configuration,定義應用程序的重定向URL


    1948913-b83927ac98110a4f.png

    1948913-d17ba89f14f06393.png
  • iOS使用Sign In with Apple在Xcode的準備工作
    在Xcode11 Signing & Capabilities中添加Sign In With Apple,如下圖


    WeChat55e9f58eb533b432073ae72ae880e823.png
  • iOS Sign In with Apple流程

  1. 導入系統頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設置ASAuthorizationAppleIDButton相關布局,并添加按鈕點擊響應事件
  2. 獲取授權碼
  3. 驗證
  1. 導入系統頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設置ASAuthorizationAppleIDButton相關布局,并添加按鈕點擊響應事件。當然蘋果也允許自定義蘋果登錄按鈕的樣式,樣式要求詳見這個文檔:Human Interface Guidelines
- (void)configUI{
    // 使用系統提供的按鈕,要注意不支持系統版本的處理
    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實現按鈕樣式
    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按鈕調用處理授權的方法
- (void)didCustomBtnClicked{
    // 封裝Sign In with Apple 登錄工具類,使用這個類時要把類對象設置為全局變量,或者直接把這個工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會導致蘋果回調不會執行
    self.signInApple = [[SignInApple alloc] init];
    [self.signInApple handleAuthorizationAppleIDButtonPress];
}

// 使用系統提供的按鈕調用處理授權的方法
- (void)didAppleIDBtnClicked{
    // 封裝Sign In with Apple 登錄工具類,使用這個類時要把類對象設置為全局變量,或者直接把這個工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會導致蘋果回調不會執行
    self.signInApple = [[SignInApple alloc] init];
    [self.signInApple handleAuthorizationAppleIDButtonPress];
}

// 處理授權
- (void)handleAuthorizationAppleIDButtonPress{
    NSLog(@"http:////////");
    
    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登錄");
    }
}
  • 注意:封裝Sign In with Apple 登錄工具類,使用這個類時要把類對象設置為全局變量,或者直接把這個工具類做成單例,如果使用局部變量,和IAP支付工具類一樣,會導致蘋果回調不會執行
  • 已經使用Sign In with Apple登錄過app的用戶
    如果設備中存在iCloud Keychain憑證或者AppleID憑證,提示用戶直接使用TouchID或FaceID登錄即可,代碼如下
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows{
    NSLog(@"http:///已經認證過了/////");
    
    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登錄");
    }
}
  • 獲取授權碼
    獲取授權碼需要在代碼中實現兩個代理回調ASAuthorizationControllerDelegate、ASAuthorizationControllerPresentationContextProviding分別用于處理授權登錄成功和失敗、以及提供用于展示授權頁面的Window,代碼如下
#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];
        
    }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(@"88888888888");
    // 返回window
    return [UIApplication sharedApplication].windows.lastObject;
}

在授權登錄成功回調中,我們可以拿到以下幾類數據

  • 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,蘋果用戶信息,包括全名、郵箱等,注意:如果玩家登錄時拒絕提供真實的郵箱賬號,蘋果會生成虛擬的郵箱賬號,而且記錄過的蘋果賬號再次登錄這些參數拿不到
  • 驗證
    關于驗證的這一步,需要傳遞授權碼給自己的服務端,自己的服務端調用蘋果API去校驗授權碼Generate and validate tokens。如果驗證成功,可以根據userIdentifier判斷賬號是否已存在,若存在,則返回自己賬號系統的登錄態,若不存在,則創建一個新的賬號,并返回對應的登錄狀態給App
  • 推薦驗證步驟為:
  • 服務端拿authorizationCode去蘋果后臺驗證,驗證地址https://appleid.apple.com/auth/token,蘋果返回id_token,與客戶端獲取的identityToken值一樣,格式如下
{
    "access_token": "一個token",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "一個token",
    "id_token": "結果是JWT,字符串形式,identityToken"
}

另外授權code是有時效性的,且使用一次即失效

  • 服務器拿到相應結果后,其中id_token是JWT數據,解碼id_token,得到如下內容
{
    "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就是授權用戶的唯一標識,與手機端獲得的user一致,服務器端通過對比sub字段信息是否與手機端上傳的user信息一致來確定是否成功登錄
該token的有效期是10分鐘,具體后端驗證參考附錄

附:原文參考鏈接
附:官方示例代碼 Swift 版
附:What the Heck is Sign In with Apple?
附:Sign In with Apple 從登陸到服務器驗證
附:蘋果授權登陸后端驗證
附:[官方文檔] Generate and validate tokens
附:[官方文檔] App Store審核指南
附:SignInAppleDemo

  • 服務端驗證原文參考鏈接

附:服務端驗證<1>
附:服務端驗證<2>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。