• 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(@"%@
    
    %@", 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

     
  • 相关阅读:
    范围截取 字符串内容
    post请求 application/x‐www‐form‐urlencoded
    未能加载文件或程序集“Newtonsoft.Json”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
    获取Url链接后的问号传值中的参数
    Post 提交跳转页面 Jquery请求
    C# POST application/x-www-form-urlencoded 请求
    《exception》第九次团队作业:Beta冲刺与验收准备(大结局)
    《Exception》第八次团队作业:Alpha冲刺
    《Exception团队》第七次作业:团队项目设计完善&编码
    《Exceptioning团队》第六次作业:团队项目系统设计改进与详细设计
  • 原文地址:https://www.cnblogs.com/ljcgood66/p/12537389.html
Copyright © 2020-2023  润新知