美文网首页
Sign In AppleID

Sign In AppleID

作者: yangli | 来源:发表于2021-01-30 16:05 被阅读0次

    第一步:导入库文件

    #import <AuthenticationServices/AuthenticationServices.h>
    

    第二步:绘制登录按钮,也可以自定义

    -(void)setupUI{
        
        if (@available(iOS 13.0, *)) {
           // Sign In With Apple Button
           ASAuthorizationAppleIDButton *appleIDButton = [ASAuthorizationAppleIDButton new];
               
           appleIDButton.frame =  CGRectMake(.0, .0, CGRectGetWidth(self.view.frame) - 40.0, 100.0);
           CGPoint origin = CGPointMake(20.0, CGRectGetMidY(self.view.frame));
           CGRect frame = appleIDButton.frame;
           frame.origin = origin;
           appleIDButton.frame = frame;
           appleIDButton.cornerRadius = CGRectGetHeight(appleIDButton.frame) * 0.25;
           [self.view addSubview:appleIDButton];
           [appleIDButton addTarget:self action:@selector(handleAuthrization:) forControlEvents:UIControlEventTouchUpInside];
           
        }
        
    }
    

    第三步:发起授权

    //! 处理授权
    - (void)handleAuthrization:(UIButton *)sender {
        if (@available(iOS 13.0, *)) {
            
            // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
            ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
            
            // 创建新的AppleID 授权请求
            ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
            // 在用户授权期间请求的联系信息
            request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
           
            /*
            // 为了执行钥匙串凭证分享生成请求的一种机制,暂时好像没啥用
            ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
            ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
            */
            
            // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
            ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
            
            // 设置授权控制器通知授权请求的成功与失败的代理
            controller.delegate = self;
            
            // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
            controller.presentationContextProvider = self;
            
            // 在控制器初始化期间启动授权流
            [controller performRequests];
            
        }
    }
    
    

    第四步:授权结果处理

    #pragma mark - Delegate
    //! 授权成功地回调
    - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization  API_AVAILABLE(ios(13.0)){
        
        NSLog(@"authorization.credential:%@", authorization.credential);
        
        NSMutableString *mStr = [NSMutableString string];
        if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
            // 用户登录使用ASAuthorizationAppleIDCredential
            ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
            
            ///凭证信息
            //用户唯一ID
            NSString *user = appleIDCredential.user;
            //授权验证信息
            NSData *token = appleIDCredential.identityToken;
            
            //用户名,邮箱信息(只有第一次授权才会带回来,以后授权成功都是返回空)
            NSString *familyName = appleIDCredential.fullName.familyName;
            NSString *givenName = appleIDCredential.fullName.givenName;
            NSString *email = appleIDCredential.email;
            
        } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
            
            // 这个获取的是iCloud记录的账号密码,需要输入框支持iOS 12 记录账号密码的新特性
            //这个回调我是没有测试出来过,不知道怎么搞
            // 用户登录使用现有的密码凭证
            ASPasswordCredential *passwordCredential = authorization.credential;
            // 密码凭证对象的用户标识 用户的唯一标识
            NSString *user = passwordCredential.user;
            // 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
            [[NSUserDefaults standardUserDefaults] setObject:user forKey:@"userIdentifier"];
            // 密码凭证对象的密码
            NSString *password = passwordCredential.password;
            
        } else {
    
            NSLog(@"授权信息均不符");
        
        }
        
    }
    
    //! 授权失败的回调
    - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error  API_AVAILABLE(ios(13.0)){
        
        NSLog(@"%s", __FUNCTION__);
        NSLog(@"错误信息:%@", 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;
        }
        NSLog(@"error:%@", errorMsg);
        
    }
    
    
    //告诉代理应该在哪个window 展示内容给用户
    - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller  API_AVAILABLE(ios(13.0)){
        
        NSLog(@"调用展示window方法:%s", __FUNCTION__);
        // 返回window
        return self.view.window;
    }
    
    

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

    • 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,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号,而且记录过的苹果账号再次登录这些参数拿不到

    验证
    关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果API去校验授权码Generate and validate tokens。如果验证成功,可以根据userIdentifier判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录状态给App

    • 推荐验证步骤为:
    • 服务端拿authorizationCode去苹果后台验证,验证地址https://appleid.apple.com/auth/token,苹果返回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分钟,具体后端验证参考附录

    第五步:已授权状态获取与监听

    1.获取已授权用户当前状态

    - (void)authorizationCredentialState{
        
        if (@available(iOS 13.0, *)) {
            
            // 基于用户的Apple ID 生成授权用户请求的机制
            ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
            
            // 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
            NSString *userIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:@"userIdentifier"];
            
            if (userIdentifier) {
                
                __block NSString *errorMsg = nil;
                //Returns the credential state for the given user in a completion handler.
                // 在回调中返回用户的授权状态
                [appleIDProvider getCredentialStateForUserID:userIdentifier completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
                    
                    switch (credentialState) {
                            // 苹果证书的授权状态
                        case ASAuthorizationAppleIDProviderCredentialRevoked:
                            // 苹果授权凭证失效,退出登录,重新授权
                            errorMsg = @"苹果授权凭证失效";
                            break;
                        case ASAuthorizationAppleIDProviderCredentialAuthorized:
                            // 苹果授权凭证状态良好
                            errorMsg = @"苹果授权凭证状态良好";
                            break;
                        case ASAuthorizationAppleIDProviderCredentialNotFound:
                            // 未发现苹果授权凭证,退出登录,重新授权
                            errorMsg = @"未发现苹果授权凭证";
                            break;
                            // 可以引导用户重新登录
                        case ASAuthorizationAppleIDProviderCredentialTransferred:
                            errorMsg = @"苹果授权信息变动";
                            break;
                    }
                }];  
            }
        }   
    }
    

    2.使用通知的方式监听授权状态变化

    //! 添加苹果登录的状态通知
    - (void)observeAppleSignInState {
        if (@available(iOS 13.0, *)) {
            NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
            [center addObserver:self selector:@selector(handleSignInWithAppleStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
        }
    }
    
    //! 观察SignInWithApple状态改变
    - (void)handleSignInWithAppleStateChanged:(id)noti {
        
        NSLog(@"%s", __FUNCTION__);
        NSLog(@"%@", noti);
    }
    
    - (void)dealloc {
        
        if (@available(iOS 13.0, *)) {
            [[NSNotificationCenter defaultCenter] removeObserver:self name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
        }
    }
    
    

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

    相关文章

      网友评论

          本文标题:Sign In AppleID

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