Sign In With Apple

苹果推出了 Sign in with Apple 功能。开始搞起来:

流程图:

Sign In With Apple

一、配置

1、需要在苹果后台打开该选项,并且重新生成Profiles配置文件,并安装到Xcode

Sign In With Apple

Sign In With Apple

 

2、服务端验证需要的文件,一个是私钥文件(.p8),一个是config.json文件(这个后面说)

先搞私钥文件:

key->添加->重命名一下,选中sign in with Apple->单击Configure按钮,然后选择你先前创建的Primary App ID

保存之后,Apple将为你生成一个新的私钥,并让你仅下载一次,请确保你保存了此文件,

因为以后你将无法再次将其取回!你下载的文件将以.p8结尾,可以将其重命名为key.txt以便在后续步骤中更轻松地使用

另外记一下你的keyID 后台会用到,就不用再来看了,如果两个APP维护,记得不要重名哦!

Sign In With Apple

Sign In With Apple

3、配置xcode11 如图点击 capability

Sign In With Apple

Sign In With Apple

Sign In With Apple

Sign In With Apple

二、代码处理

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个参数之后,发起请求 Sign In With Apple     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

Sign In With Apple

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

 

 

 

 

上一篇:PHP-MVC,什么是填充我的演示模型的最佳实践?


下一篇:Objective C Char *,Const Char *,Cstring,CFString互转