Sign In AppleID

第一步:导入库文件

#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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容