Sign in with Apple (通過Apple 登錄)

在 WWDC 2019 上,蘋果推出了自家的 Sign in with Apple 功能,這很 Apple。可能蘋果看到第三方登錄百家爭鳴,琢磨着自己也搞了個,這對很多第三方登錄來說可能是個威脅。

使用 Sign in with Apple 會更加方便、快捷、安全,蘋果不會追蹤用戶在應用中的行爲。所以,對於用戶來說使用 Sign in with Apple 會更加安全。

另外,Sign in with Apple 支持跨平臺

∙ Native SDK 支持 iOS/MacOS/watchOS/tvOS

∙ Javascript SDK 支持  Android, Windows, Web

話說這個 iOS 13 才支持的功能,我們有必要集成嗎?

看了下面這句話,你或許就有答案了

Sign In with Apple will be available for beta testing this summer.
 It will be required as an option for users in apps that
 support third-party sign-in when it is commercially available later this year.

簡單來說,如果你的 App 沒有提供第三方登錄,那就不用集成。如果用到了第三方登錄,那麼需要提供 Sign in with Apple。

集成

一、後臺設置

開啓 Sign in with Apple 功能

1. 登錄開發者網站,在需要添加 Sign in with Apple 功能的 Identifier 開啓功能。

2. Xcode 裏面 Signing & Capabilities 開啓 Sign in with Apple 功能。

二、實現

1、蘋果提供的按鈕

官方提供了一個 ASAuthorizationAppleIDButton (繼承自UIControl),使用這個來創建一個登錄按鈕。

ASAuthorizationAppleIDButton *loginBtn = [[ASAuthorizationAppleIDButton alloc]initWithAuthorizationButtonType:ASAuthorizationAppleIDButtonTypeSignIn authorizationButtonStyle:ASAuthorizationAppleIDButtonStyleWhite];
        [loginBtn addTarget:self action:@selector(signInWithApple) forControlEvents:UIControlEventTouchUpInside];
        loginBtn.center = self.view.center;
        loginBtn.bounds = CGRectMake(0, 0, 200, 40);
        [self.view addSubview:loginBtn];

這個按鈕具有兩種文案類型和三個樣式,分別是:

typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonType) {
    ASAuthorizationAppleIDButtonTypeSignIn,
    ASAuthorizationAppleIDButtonTypeContinue,

    ASAuthorizationAppleIDButtonTypeDefault = ASAuthorizationAppleIDButtonTypeSignIn,
} 


typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonStyle) {
    ASAuthorizationAppleIDButtonStyleWhite,
    ASAuthorizationAppleIDButtonStyleWhiteOutline,
    ASAuthorizationAppleIDButtonStyleBlack,
} 

樣式

從圖上可以看出:

∙ Apple 提供的登錄按鈕有三種外觀:白色,帶有黑色輪廓線的白色和黑色。

∙ 文案有兩種:Sign In with Apple 和 Continue with Apple。(具體使用哪個文案,根據自身業務需求來定)

另外,按鈕寬高默認值爲 {width:130, height:30}

對於 ASAuthorizationAppleIDButton 我們能夠自定義的東西比較少,比如背景色不能更改,文案只有兩種可選,並且值不能修改,可以調整的只有圓角cornerRadiussize 。

本地化:必要且重要的一點

 

2、Authorization 發起授權登錄請求

#pragma mark- 點擊登錄
-(void)signInWithApple API_AVAILABLE(ios(13.0))
{
    ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
    ASAuthorizationAppleIDRequest * request = [provider createRequest];
    request.requestedScopes = @[ASAuthorizationScopeFullName,ASAuthorizationScopeEmail];
    
    ASAuthorizationController *vc= [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[request]];
    vc.delegate = self;
    vc.presentationContextProvider = self;
    
    [vc performRequests];
}

ASAuthorizationAppleIDProvider 這個類比較簡單,頭文件中可以看出,主要用於創建一個 ASAuthorizationAppleIDRequest 以及獲取對應 userID 的用戶授權狀態。在上面的方法中我們主要是用於創建一個 ASAuthorizationAppleIDRequest ,用戶授權狀態的獲取後面會提到。

∙ 給創建的 request 設置 requestedScopes ,這是個 ASAuthorizationScope 數組,目前只有兩個值,ASAuthorizationScopeFullName 和 ASAuthorizationScopeEmail,根據需求去設置即可。

∙ 然後,創建 ASAuthorizationController ,它是管理授權請求的控制器,給其設置 delegate 和 presentationContextProvider ,最後啓動授權 performRequests 。

設置上下文

ASAuthorizationControllerPresentationContextProviding 就一個方法,主要是告訴 ASAuthorizationController 展示在哪個 window 上。

-(ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller
API_AVAILABLE(ios(13.0)){
   return  self.view.window;
}

3. Verification 授權

用戶發起授權請求後,系統就會彈出用戶登錄驗證的頁面。

 

在用戶沒有同意授權之前或者取消授權之後,點擊登錄的時候,都會彈出上面這個界面,在這個授權頁面,我們可以修改自己的用戶名,以及可以選擇共享我的電子郵箱或者隱藏郵件地址。這樣一來,就可以達到隱藏自己真實信息的目的。

授權一次後,再次點擊登錄按鈕,則會直接彈出下面這個窗口:

授權回調處理

下面是 ASAuthorizationControllerDelegate 方法,一個是授權成功的回調,一個是失敗的回調。

#pragma mark- 授權成功的回調
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization
API_AVAILABLE(ios(13.0)){
    
    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        
        ASAuthorizationAppleIDCredential * credential = authorization.credential;
        
        NSString *state = credential.state;
        
        NSString * userID = credential.user;
        
        NSPersonNameComponents *fullName = credential.fullName;
        NSString * email = credential.email;
        //refresh token
        NSString * authorizationCode = [[NSString alloc]initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding];
        // access token
        NSString * identityToken = [[NSString alloc]initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
        
        ASUserDetectionStatus realUserStatus = credential.realUserStatus;
               NSLog(@"state: %@", state);
               NSLog(@"userID: %@", userID);
               NSLog(@"fullName: %@", fullName);
               NSLog(@"email: %@", email);
               NSLog(@"authorizationCode: %@", authorizationCode);
               NSLog(@"identityToken: %@", identityToken);
               NSLog(@"realUserStatus: %@", @(realUserStatus));
    }
   
}

#pragma mark- 授權失敗的回調
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error
API_AVAILABLE(ios(13.0)){
    
    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;
    
    }
  
}

 當我們授權成功後,我們可以在 authorizationController:didCompleteWithAuthorization: 這個代理方法中獲取到 ASAuthorizationAppleIDCredential ,通過這個可以拿到用戶的 userIDemailfullNameauthorizationCodeidentityToken 以及 realUserStatus 等信息。

 

這些信息具體含義和用途:

∙ User ID: Unique, stable, team-scoped user ID,蘋果用戶唯一標識符,該值在同一個開發者賬號下的所有 App 下是一樣的,開發者可以用該唯一標識符與自己後臺系統的賬號體系綁定起來。

∙ Verification data: Identity token, code,驗證數據,用於傳給開發者後臺服務器,然後開發者服務器再向蘋果的身份驗證服務端驗證本次授權登錄請求數據的有效性和真實性,詳見 Sign In with Apple REST API。如果驗證成功,可以根據 userIdentifier 判斷賬號是否已存在,若存在,則返回自己賬號系統的登錄態,若不存在,則創建一個新的賬號,並返回對應的登錄態給 App。

∙ Account information: Name, verified email,蘋果用戶信息,包括全名、郵箱等。

∙ Real user indicator: High confidence indicator that likely real user,用於判斷當前登錄的蘋果賬號是否是一個真實用戶,取值有:unsupportedunknownlikelyReal

失敗情況會走 authorizationController:didCompleteWithError: 這個方法,具體看代碼吧。

5. Handling Changes

通過上面的步驟一個完整的授權,已經完成。BUT,我們還需要處理一些 Case。

∙ 用戶終止 App 中使用 Sign in with Apple 功能

∙ 用戶在設置裏註銷了 AppleId

這些情況下,App 需要獲取到這些狀態,然後做退出登錄操作,或者重新登錄。
我們需要在 App 啓動的時候,通過 getCredentialState:completion: 來獲取當前用戶的授權狀態。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    if (@available(iOS 13.0, *)) {
        NSString *userIdentifier = 鑰匙串中取出的 userIdentifier;
        if (userIdentifier) {
            ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
            [appleIDProvider getCredentialStateForUserID:userIdentifier
                                              completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState,
                                                           NSError * _Nullable error)
            {
                switch (credentialState) {
                    case ASAuthorizationAppleIDProviderCredentialAuthorized:
                        // The Apple ID credential is valid
                        break;
                    case ASAuthorizationAppleIDProviderCredentialRevoked:
                        // Apple ID Credential revoked, handle unlink
                        break;
                    case ASAuthorizationAppleIDProviderCredentialNotFound:
                        // Credential not found, show login UI
                        break;
                }
            }];
        }
    }
    
    return YES;
}

ASAuthorizationAppleIDProviderCredentialState 解析如下:

ASAuthorizationAppleIDProviderCredentialAuthorized 授權狀態有效;∙ASAuthorizationAppleIDProviderCredentialRevoked 上次使用蘋果賬號登錄的憑據已被移除,需解除綁定並重新引導用戶使用蘋果登錄;

ASAuthorizationAppleIDProviderCredentialNotFound 未登錄授權,直接彈出登錄頁面,引導用戶登錄。

另外,在 App 使用過程中,你還可以通過通知方法來監聽 revoked 狀態,可以添加 ASAuthorizationAppleIDProviderCredentialRevokedNotification 這個通知,收到這個通知的時候,我們可以:

∙ Sign user out on this device

∙ Guide to sign in again

具體怎麼添加和處理,可以根據業務需求來決定。

 

- (void)observeAppleSignInState
{
    if (@available(iOS 13.0, *)) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleSignInWithAppleStateChanged:)
                                                     name:ASAuthorizationAppleIDProviderCredentialRevokedNotification
                                                   object:nil];
    }
}

- (void)handleSignInWithAppleStateChanged:(NSNotification *)notification
{
    // Sign the user out, optionally guide them to sign in again
    NSLog(@"%@", notification.userInfo);
}

One more thing
除此之外,蘋果還把 iCloud KeyChain password 集成到了這套 API 裏,我們在使用的時候,只需要在創建 request 的時候,多創建一個 ASAuthorizationPasswordRequest,這樣如果 KeyChain 裏面也有登錄信息的話,可以直接使用裏面保存的用戶名和密碼進行登錄。代碼如下: 

- (void)perfomExistingAccountSetupFlows API_AVAILABLE(ios(13.0))
{
    ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
    ASAuthorizationAppleIDRequest *authAppleIDRequest = [appleIDProvider createRequest];
    ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];
    
    NSMutableArray <ASAuthorizationRequest *>* array = [NSMutableArray arrayWithCapacity:2];
    if (authAppleIDRequest) {
        [array addObject:authAppleIDRequest];
    }
    if (passwordRequest) {
        [array addObject:passwordRequest];
    }
    NSArray <ASAuthorizationRequest *>* requests = [array copy];
    
    ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
    authorizationController.delegate = self;
    authorizationController.presentationContextProvider = self;
    [authorizationController performRequests];
}

#pragma mark - ASAuthorizationControllerDelegate

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
{
    if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
        ASPasswordCredential *passwordCredential = authorization.credential;
        NSString *userIdentifier = passwordCredential.user;
        NSString *password = passwordCredential.password;
        
        NSLog(@"userIdentifier: %@", userIdentifier);
        NSLog(@"password: %@", password);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章