Sign In With Apple


蘋果推出了 Sign in with Apple 功能(支持三方登陸的也必須支持蘋果的)。

快到最后時限了還是搞一下吧,沖沖沖:

流程圖:

一、配置

1、需要在蘋果后台打開該選項,並且重新生成Profiles配置文件,並安裝到Xcode

 

2、服務端驗證需要的文件,一個是私鑰文件(.p8),一個是config.json文件(這個后面說)

先搞私鑰文件:

key->添加->重命名一下,選中sign in with Apple->單擊Configure按鈕,然后選擇你先前創建的Primary App ID

保存之后,Apple將為你生成一個新的私鑰,並讓你僅下載一次請確保你保存了此文件,

因為以后你將無法再次將其取回!你下載的文件將以.p8結尾,可以將其重命名為key.txt以便在后續步驟中更輕松地使用

另外記一下你的keyID 后台會用到,就不用再來看了,如果兩個APP維護,記得不要重名哦!

3、配置xcode11 如圖點擊 capability

二、代碼處理(OC,需要swift的看最后的參考鏈接,不過邏輯都是一樣的)

1、導入系統頭文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登錄按鈕,設置ASAuthorizationAppleIDButton相關布局,並添加按鈕點擊響應事件

1.1 Sign In with Apple登錄按鈕有很多種樣式可以修改style查看

1.2 這里我使用的通知獲取蘋果返回的驗證信息(

viewDidLoad添加通知,dealloc移除通知

1.3 這里把獲取蘋果返回的驗證信息工具類做成單例(具體代碼見2)

注意: 蘋果給的驗證信息后台只能驗證一次,並且是5分鍾內,所以這里的登錄操作不要重復,不然下次的重復登錄會token驗證失敗

    // sign in with apple
    // 使用系統提供的按鈕,要注意不支持系統版本的處理
       if (@available(iOS 13.0, *)) {
           vOrLine.hidden = YES;
           // Sign In With Apple Button
           ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhiteOutline];
           appleIDBtn.frame = CGRectMake(30, 5, v.width - 60, 40);
           [appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];
           [v addSubview:appleIDBtn];
           [[NSNotificationCenter defaultCenter] addObserver:self
                                                       selector:@selector(signInApple:)
                                                           name:NOTIFICATION_SignInApple
                                                         object:nil];
       }
// 使用系統提供的按鈕調用處理授權的方法
- (void)didAppleIDBtnClicked{
    // 封裝Sign In with Apple
    [[DhSignInApple shareInstance] handleAuthorizationAppleIDButtonPress];
}

-(void)signInApple : (NSNotification *)notification{
    NSDictionary *userInfo = notification.userInfo;
   // 獲取到數據 進行后台登錄
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

2、獲取授權碼並驗證

2.1這里我把昵稱也返給后台

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@class DhSignInApple;
typedef void(^success) (NSDictionary *dic);
typedef void(^failure) (NSString *errorMsg);

@interface DhSignInApple : NSObject

@property (nonatomic, copy)success  successblock; //
@property (nonatomic, copy)failure  failureblock; //

//實例化對象
+ (instancetype)shareInstance;
// 處理授權
- (void)handleAuthorizationAppleIDButtonPress;
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows;
@end

NS_ASSUME_NONNULL_END
#import "DhSignInApple.h"
#import"Helper.h"

#import <AuthenticationServices/AuthenticationServices.h>
@interface DhSignInApple ()<ASAuthorizationControllerDelegate,ASAuthorizationControllerPresentationContextProviding>

@end
@implementation DhSignInApple
//實例化對象
+ (instancetype)shareInstance
{
    static DhSignInApple *instance = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DhSignInApple new];
    });
    return instance;
}
// 處理授權
- (void)handleAuthorizationAppleIDButtonPress{
    
    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登錄");
    }
}
// 如果存在iCloud Keychain 憑證或者AppleID 憑證提示用戶
- (void)perfomExistingAccountSetupFlows{
    NSLog(@"///已經認證過了/////");
    
    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登錄");
    }
}
#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];
       // nick_name
        NSString *name;
        if (familyName) {
            name = familyName;
        }
        if (givenName) {
            if (name.length > 0) {
                name = [NSString stringWithFormat:@"%@%@",name,givenName];
            }else{
                name = givenName;
            }
         }
        if(user.length > 0){
            dispatch_async(dispatch_get_main_queue(), ^{
                if (name.length > 0) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SignInApple object:nil userInfo:@{@"ios_uid":user,@"nick_name":name,@"identity_token":identityTokenStr,@"authorization_code":authorizationCodeStr}];
                }else{
                    [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SignInApple object:nil userInfo:@{@"ios_uid":user,@"identity_token":identityTokenStr,@"authorization_code":authorizationCodeStr}];
                }
            });
        }
               
    }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(@"---window");
    // 返回window
    return [UIApplication sharedApplication].windows.lastObject;
}

@end

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

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,蘋果用戶信息,包括全名、郵箱等,

注意:如果玩家登錄時拒絕提供真實的郵箱賬號,蘋果會生成虛擬的郵箱賬號,而且記錄過的蘋果賬號再次登錄這些參數拿不到  。使用過授權的,下次就獲取不到Name, email這些了。測試時可以進入設置密碼與安全性--使用您Apple ID的APP--進行編輯刪除操作,再次重新授權!

 

三、服務端

以上,從客戶端拿到user 、 identityToken 、 authorizationCode 等相關信息。那么怎么來校驗信息的正確性呢?

1、對客戶端傳遞過來的 identityToken做個校驗,以某一次授權拿到的數據來舉個例子。在上文的授權回調中拿到的 identityToken來驗證,得到如下結果
identityToken = "eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmZjYm94LmhpdmVjb25zdW1lciIsImV4cCI6MTU3NjIxNjc3NSwiaWF0IjoxNTc2MjE2MTc1LCJzdWIiOiIwMDE4NTcuNDBhODZjNDM4MzMwNDczNDgzZjk1YzcyMDA3MzY2YTYuMTAxNSIsImNfaGFzaCI6IjNCQlc1bWlaR3ZEX3RzNkNZdlUwR0EiLCJlbWFpbCI6Im45cHZ2Zms2cnNAcHJpdmF0ZXJlbGF5LmFwcGxlaWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiaXNfcHJpdmF0ZV9lbWFpbCI6InRydWUiLCJhdXRoX3RpbWUiOjE1NzYyMTYxNzV9.Nksq3o1E8UxD4V7GmJB7ZrS0vSj_mm_ybdo7eiSbbAYNk6RnLuaRiJQYtI64mkZ-TqdeBgJmWt5bcSrW1gsWYk85YGeK79cIHaYO7nRIX1-e3_ociEJ3_dCECThrp-aMKZzq0yDz-xzbokZVsI4WmPcKlqhuE6ul2FBHwQrT3bTnxk_jB_4htqGjSW9u2cp2m-WbLrCgsorND3Z7w4KBICcEMqRnVbjTijO__-sgreXrFwDPu3LzccGQMr9cOugJorEe7gIEnACfOSF40YrsZ344SZfZ0VK9O8zOp6BoWw3yORDQiHkRjS0V9Tmi5SHQCGZ17kbjlrPUOQA0HgsVTQ"
 
2、服務端向蘋果請求驗證,服務器通過  https://appleid.apple.com/auth/token 該接口,
並拼接指定的參數去驗證,接口相關信息蘋果有提供  Generate and validate tokens 。請求參數說明
client_idapp的  bundle identifier
client_secret: 需要我們自己生成,下文講解生成方法
code: 即為手機端獲取到的  authorizationCode 信息
grant_type: 固定字符串  authorization_code  
拿到上面4個參數之后,發起請求
 
 
3、生成 client_secret。下文代碼為  Ruby 代碼,確保已安裝 ruby環境。創建一個 secret_gen.rb文件,把下面的代碼拷貝進去。執行 ruby secret_gen.rb即可生成 client_secret
require "jwt"

key_file = "客戶端步驟2中生成的私鑰.p8文件的路徑"
team_id = "Team ID"
client_id = "Bundle ID"
key_id = "Key ID"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.

private_key = OpenSSL::PKey::EC.new IO.read key_file

token = JWT.encode(
  {
    iss: team_id,
    iat: Time.now.to_i,
    exp: Time.now.to_i + 86400 * validity_period,
    aud: "https://appleid.apple.com",
    sub: client_id
  },
  private_key,
  "ES256",
  header_fields=
  {
    kid: key_id 
  }
)
puts token

其他編程語言生成client_secret代碼,參考這個吧,或許有用
https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
https://www.jianshu.com/p/2342260c95ff

 

team_id 如何獲取 登錄后在這里看
https://developer.apple.com/account/#/membership/

 

4、把生成的client_secret代入步驟2中,得到的參數解釋看這個文檔,拿到id_token其也是一個JWT數據,回到JWT官網decode 出 payload 部分

 

5、驗證結果
比對服務端步驟1和步驟4圖中的 audsub是否一致,若信息一致確定成功登錄,其中 audappbundleID。由於沒有涉及到網頁登錄所以並沒有集成 iCloud KeyChain password
以上就是關於  Sign in with Apple 的相關內容和集成方法。
 
 
              

 參考:

https://www.jianshu.com/p/e1284bd8c72a

https://www.jianshu.com/p/4523c72c50bd

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM