admin管理员组

文章数量:1530013

应用集成第三方登录,iOS 13之后必须集成苹果登录,否则审核会被拒的。较为常用的第三方登录是微信和QQ,微信不提供网页登录授权,所以用户手机没有安装微信最好是不展示微信登录入口,QQ是提供网页授权登录的,所以提供的第三方登录入口QQ可以不加判断直接展示。苹果登录只在iOS 13以后才可以使用,所以系统版本小于13的也不能展示苹果入口。
微信和QQ不多说,直接在开发平台注册应用,拿到APPID和APP secret去调用微信和QQ的SDK,处理对应的回调,在Xcode设置URL Schemes和白名单。

开发Sign In with Apple的注意事项
需要在苹果后台打开该选项,并且重新生成Profiles配置文件,并安装到Xcode,如下图

  • 服务端验证需要的文件,一个是私钥文件,一个是config.json文件

  • 创建用于客户端身份验证的私钥
    返回Certificates, Identifiers & Profiles主屏幕,从侧面导航中选择Keys

单击Configure按钮,然后选择你先前创建的Primary App ID,保存之后,Apple将为你生成一个新的私钥,并让你仅下载一次,请确保你保存了此文件,因为以后你将无法再次将其取回!你下载的文件将以.p8结尾,可以将其重命名为key.txt以便在后续步骤中更轻松地使用

  • 创建config.json新文件,格式、内容和参数说明如下
{
    "client_id": "实际上被称为“Service ID”,您将在“Identifiers”部分创建它,其实就是应用的bundleID",
    "team_id": "后台账号的teamID",
    "redirect_uri": "重定向url,网页登录需要,只是客服端登录可以不写",
    "key_id": "在苹果后台获取,如下图",
    "scope": "设置我们要从用户那里收集什么信息,我们可以设置email和name,或者也可以不写
}

  • web使用Sign In with Apple的相关配置,不需要web登录的,以下配置可以忽略
  • 创建Services ID

在下一步中,你将定义用户在登录流程中将看到的应用程序的名称,并定义成为OAuth的标识符client_id,确保还选中Sign In with Apple复选框

创建web Authentication Configuration,定义应用程序的重定向URL

iOS使用Sign In with AppleXcode的准备工作
Xcode11 Signing & Capabilities中添加Sign In With Apple,如下图

  • iOS Sign In with Apple流程

  1. 导入系统头文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登录按钮,设置ASAuthorizationAppleIDButton相关布局,并添加按钮点击响应事件
  2. 获取授权码
  3. 验证

 


 

  1. 导入系统头文件#import <AuthenticationServices/AuthenticationServices.h>,添加Sign In with Apple登录按钮,设置ASAuthorizationAppleIDButton相关布局,并添加按钮点击响应事件。当然苹果也允许自定义苹果登录按钮的样式,样式要求详见这个文档:Human Interface Guidelines
    - (void)configUI{
        // 使用系统提供的按钮,要注意不支持系统版本的处理
        if (@available(iOS 13.0, *)) {
            // Sign In With Apple Button
            ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
            appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180, self.view.bounds.size.width - 60, 100);
            //    appleBtn.cornerRadius = 22.f;
            [appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked) forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:appleIDBtn];
        }
        
        // 或者自己用UIButton实现按钮样式
        UIButton *addBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        addBtn.frame = CGRectMake(30, 80, self.view.bounds.size.width - 60, 44);
        addBtn.backgroundColor = [UIColor orangeColor];
        [addBtn setTitle:@"Sign in with Apple" forState:UIControlStateNormal];
        [addBtn addTarget:self action:@selector(didCustomBtnClicked) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:addBtn];
    }
    
    // 自己用UIButton按钮调用处理授权的方法
    - (void)didCustomBtnClicked{
        // 封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行
        self.signInApple = [[SignInApple alloc] init];
        [self.signInApple handleAuthorizationAppleIDButtonPress];
    }
    
    // 使用系统提供的按钮调用处理授权的方法
    - (void)didAppleIDBtnClicked{
        // 封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行
        self.signInApple = [[SignInApple alloc] init];
        [self.signInApple handleAuthorizationAppleIDButtonPress];
    }
    
    // 处理授权
    - (void)handleAuthorizationAppleIDButtonPress{
        NSLog(@"");
        
        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登录");
        }
    }
    

     

  2. 注意:封装Sign In with Apple 登录工具类,使用这个类时要把类对象设置为全局变量,或者直接把这个工具类做成单例,如果使用局部变量,和IAP支付工具类一样,会导致苹果回调不会执行
  3. 已经使用Sign In with Apple登录过app的用户
    如果设备中存在iCloud Keychain凭证或者AppleID凭证,提示用户直接使用TouchIDFaceID登录即可,代码如下
    // 如果存在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登录");
        }
    }
    

    2.获取授权码
    获取授权码需要在代码中实现两个代理回调ASAuthorizationControllerDelegate、ASAuthorizationControllerPresentationContextProviding分别用于处理授权登录成功和失败、以及提供用于展示授权页面的Window,代码如下

    #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];
            
        }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(@"88888888888");
        // 返回window
        return [UIApplication sharedApplication].windows.lastObject;
    }
    

    在授权登录成功回调中,我们可以拿到以下几类数据

  4. UserID:Unique, stable, team-scoped user ID,苹果用户唯一标识符,该值在同一个开发者账号下的所有App下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来(这与国内的微信、QQ、微博等第三方登录流程基本一致)
  5. Verification data:Identity token, code,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性,详见Sign In with Apple REST API
  6. Account information:Name, verified email,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到
  7. 验证
    关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果API去校验授权码Generate and validate tokens。如果验证成功,可以根据userIdentifier判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录状态给App
  8. 推荐验证步骤为:
  9. 服务端拿authorizationCode去苹果后台验证,验证地址https://appleid.apple/auth/token,苹果返回id_token,与客户端获取的identityToken值一样,格式如下
    {
        "access_token": "一个token",
        "token_type": "Bearer",
        "expires_in": 3600,
        "refresh_token": "一个token",
        "id_token": "结果是JWT,字符串形式,identityToken"
    }
    

    另外授权code是有时效性的,且使用一次即失效

  10. 服务器拿到相应结果后,其中id_tokenJWT数据,解码id_token,得到如下内容
    {
        "iss":"https://appleid.apple",
        "aud":"这个是你的app的bundle identifier",
        "exp":1567482337,
        "iat":1567481737,
        "sub":"这个字段和客户端获取的user字段是完全一样的",
        "c_hash":"8KDzfalU5kygg5zxXiX7dA",
        "auth_time":1567481737
    }
    

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

    附:官方示例代码 Swift 版
    附:What the Heck is Sign In with Apple?
    附:Sign In with Apple 从登陆到服务器验证
    附:苹果授权登陆后端验证
    附:[官方文档] Generate and validate tokens
    附:[官方文档] App Store审核指南

本文标签: 苹果最新