美文网首页
iOS 13 Sign In with Apple

iOS 13 Sign In with Apple

作者: HF_K | 来源:发表于2020-07-23 14:39 被阅读0次

    iOS 13新增功能Sign In with AppleSign In with Apple是跨平台的,可以支持iOSmacOSwatchOStvOSJS。最近上架AppStore因为没有Sign In with Apple被拒了,本文主要内容为Sign In with AppleiOS上的使用。

    被拒原因

    项目配置

    1. 苹果开发者网站,在对应的AppleId下面开通Sign In With Apple的权限。如下图

      设置SignInWithApple
    2. 现在我只需要在Signing & Capabilities中添加Sign In With AppleXcode会自动帮你配置生成相关文件。其实上面第一步可以省略,这里可以自动生成。

    Xcode中设置 配置结果

    代码集成

    Sign In With Apple流程

    1. 导入系统头文件#import <AuthenticationServices/AuthenticationServices.h>,创建相关Sign In with Apple登录按钮,添加按钮点击响应事件
    2. 获取授权码
    3. 验证
    4. 执行自己的相关逻辑

    注意⚠️:创建按钮可以自定义或者使用系统提供的按钮。

    实际操作

    1. 导入头文件

       #import <AuthenticationServices/AuthenticationServices.h>
      
    2. 创建对应UI

       -(void)setUI{
           // 使用系统提供的按钮,要注意不支持系统版本的处理
           if (@available(iOS 13.0, *)) {
               // Sign In With Apple Button
               ASAuthorizationAppleIDButton *btn_apple = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
               btn_apple.frame = CGRectMake(50, 100,  self.view.bounds.size.width - 100, 100);
               [btn_apple addTarget:self action:@selector(signWithApple:) forControlEvents:UIControlEventTouchUpInside];
               [self.view addSubview:btn_apple];
           }
           // 或者自己用UIButton实现按钮样式
           UIButton *btn_custom = [UIButton buttonWithType:UIButtonTypeCustom];
           btn_custom.frame = CGRectMake(50, 250, self.view.bounds.size.width - 100, 44);
           [btn_custom setTitle:@"Sign in with Apple" forState:UIControlStateNormal];
           [btn_custom setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
       
           [btn_custom addTarget:self action:@selector(signWithApple:) forControlEvents:UIControlEventTouchUpInside];
           [self.view addSubview:btn_custom];
           
       }
       -(void)signWithApple:(UIButton *)sender{
           [[SignWithAppleManger sharedManager]SignInWithAppleWithBlock:^(id  _Nonnull data, BOOL success, BOOL showError) {
               NSLog(@"123");
           }];
       }
      

    注意⚠️:封装Sign In with Apple登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行。已经使用Sign In with Apple登录过App的用户如果设备中存在iCloud Keychain凭证或者AppleID凭证,提示用户直接使用TouchIDFaceID登录即可。

        -(void)ExistingSignInWithAppleWithBlock:(SignBackInfoBlock)block{
            
            self.block = block;
            
            [self observeAppleSignInState];
            
            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];
                NSMutableArray <ASAuthorizationRequest *>* requestArr = [NSMutableArray arrayWithCapacity:2];
                if (appleIdRequest) {
                    [requestArr addObject:appleIdRequest];
                }
                if (passwordRequest) {
                    [requestArr addObject:passwordRequest];
                }
                // ASAuthorizationRequest:对于不同种类授权请求的基类
                NSArray <ASAuthorizationRequest *>* requests = [requestArr copy];
                // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
                ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
                // 设置授权控制器通知授权请求的成功与失败的代理
                authorizationController.delegate = self;
                // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
                authorizationController.presentationContextProvider = self;
                // 在控制器初始化期间启动授权流
                [authorizationController performRequests];
            }else{
                // 处理不支持系统版本
                if (self.block) {
                    self.block(@"该系统版本不可用Apple登录", NO, NO);
                }
            }
        }
    
    1. 获取授权码
      获取授权码需要在代码中实现两个代理回调ASAuthorizationControllerDelegateASAuthorizationControllerPresentationContextProviding分别用于处理授权登录成功和失败。

       #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 = (ASAuthorizationAppleIDCredential *)authorization.credential;
               NSString *user = appleIDCredential.user;
               // 使用钥匙串的方式保存用户的唯一信息
               [SAMKeychain setPassword:user forService:[NSBundle mainBundle].bundleIdentifier account:ShareCurrentIdentifier];
               // 使用过授权的,可能获取不到以下三个参数
               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];
               if (self.block) {
                   self.block(user, YES, NO);
               }
           }else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
               // 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性,如果不支持,可以忽略
               // Sign in using an existing iCloud Keychain credential.
               // 用户登录使用现有的密码凭证
               ASPasswordCredential *passwordCredential = (ASPasswordCredential *)authorization.credential;
               // 密码凭证对象的用户标识 用户的唯一标识
               NSString *user = passwordCredential.user;
               // 密码凭证对象的密码
               NSString *password = passwordCredential.password;
           }else{
               if (self.block) {
                   self.block(@"授权信息均不符", NO, NO);
               }
           }
       }
       
       //MARK:授权失败的回调
       - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
           // Handle 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;
           }
           if (self.block) {
               self.block(errorMsg, NO, YES);
           }
       }
       //MARK ASAuthorizationControllerPresentationContextProviding
       //告诉代理应该在哪个window 展示内容给用户
       - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
           // 返回window
           return [UIApplication sharedApplication].windows.lastObject;
       }
      

    注意⚠️:完成上述后一般会返回需要的数据,如下

    1. UserID:Unique, stable, team-scoped user ID,苹果用户唯一标识符,该值在同一个开发者账号下的所有App下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
    2. Verification data:Identity token, code,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性
    3. Account information:Name, verified email,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到。
    4. 验证,关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果API去校验授权码Generate and validate tokens

    注意⚠️:验证一般是app端获取到信息,然后把authorizationCode传给后台,验证地址,苹果返回id_token,与客户端获取的identityToken值一样,格式如下

    {
        "access_token": "一个token",
        "token_type": "Bearer",
        "expires_in": 3600,
        "refresh_token": "一个token",
        "id_token": "结果是JWT,字符串形式,identityToken"
    }
    

    授权code是有时效性的,且使用一次即失效
    服务器拿到相应结果后,其中id_tokenJWT数据,解码id_token,得到如下内容

    {
        "iss":"https://appleid.apple.com",
        "aud":"这个是你的app的bundle identifier",
        "exp":1567482337,
        "iat":1567481737,
        "sub":"这个字段和客户端获取的user字段是完全一样的",
        "c_hash":"8KDzfalU5kygg5zxXiX7dA",
        "auth_time":1567481737
    }
    

    其中aud与你appbundleID一致,sub就是授权用户的唯一标识,与手机端获得的user一致,服务器端通过对比sub字段信息是否与手机端上传的user信息一致来确定是否成功登录
    token的有效期是10分钟。

    Demo

    相关文章

      网友评论

          本文标题:iOS 13 Sign In with Apple

          本文链接:https://www.haomeiwen.com/subject/lichlktx.html