苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走



这两个方法都没有调用。
///回调成功
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
}



///回调失败

-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){}

总结下,我做这块时的疑问的问题。

问题1:苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError都没有调用?

解决方案:经过测试,发现我的这些处理苹果登陆代码是写到继承NSObject的类时,不会调用。换成继承UIViewController时就可以调用成功了。或者继续用继承NSObject ,但是这个类要 用单利写也是可以调用成功的。单利代码示例


//  GSSignWithApple.h 文件

@interface GSSignWithApple : NSObject


+ (GSSignWithApple *)SharedManager;

@end





//  GSSignWithApple.m 文件

+ (GSSignWithApple *)SharedManager{
    static GSSignWithApple *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //不能再使用alloc方法
        //因为已经重写了allocWithZone方法,所以这里要调用父类的分配空间的方法
    manager = [[super allocWithZone:NULL] init];
    });
    return manager;
}



// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [GSSignWithApple SharedManager];
}

//下面是初始化和 几个代理方法 和 点击事件的处理 

问题2:不用ASAuthorizationAppleIDButton写可以吗?用UIUButtom写可以吗?UI是用h5写的钮怎么调用的?

答:都可以。

我这边都试过了。都可以调用。我这边页面是H5写的,就没初始化按钮了,直接从通常的按钮点击事件的代码开始写的如下



//这块代码一般是初始化苹果登陆按钮后,苹果按钮的点击事件。后面的步骤跟正常的苹果登陆代码一样。
-(void)click API_AVAILABLE(ios(13.0)){

    ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc]init];

    ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];

    request.requestedScopes = @[ASAuthorizationScopeFullName,ASAuthorizationScopeEmail];

    ASAuthorizationController *auth = [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[request]];

    auth.delegate = self;

    auth.presentationContextProvider = self;

    [auth performRequests];

}

 

问题3:iOS负责啥,后台复杂啥?具体看后面的文章,iOS主要是负责获取用户的 信息和token 、code 、 这些要传给后台,后台要对接苹果官网接口。其实就跟对接微信登陆步骤差不多。只不过苹果的加密 验证比较繁琐些。

 

 

下面是具体的实现步骤。我复制别人的。

作者yuyangkk     https://www.jianshu.com/p/12aade4d053b

作者72行代码     https://www.jianshu.com/p/e1284bd8c72a

 

1.背景

根据苹果审核指南要求,但凡接入三方登录的,必须要接入苹果授权登录,因我们App中接入了微信登录,所以按照要求添加苹果登录。

苹果审核指南-4.8 通过 Apple 登录
如果 app 专门使用第三方或社交登录服务 (例如,Facebook 登录、Google 登录、通过 Twitter 登录、通过 LinkedIn 登录、通过 Amazon 登录或微信登录) 来对其进行设置或验证这个 app 的用户主帐户,则该 app 必须同时提供“通过 Apple 登录”作为等效选项。用户的主帐户是指在 app 中建立的、用于标识身份、登录和访问功能和相关服务的帐户。
在以下情况下,不要求提供“通过 Apple 登录”选项:
您的 app 仅使用公司自有的帐户设置和登录系统。
您的 app 是一款教育、企业或商务 app,要求用户使用现有的教育或企业帐户登录。
您的 app 使用政府或行业支持的公民身份系统或电子身份证来鉴定用户身份。
您的 app 是特定第三方服务的客户端,用户需要使用他们的邮件、社交媒体或其他第三方帐户直接登录才能访问内容。

2.接入准备工作

2.1 开发者网站,开启对应的Sign in With Apple

勾选Sign In With Apple,点击Edit

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第1张图片

2.1.1已经存在的App.png

选中Enable as a primary App ID

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第2张图片

2.1.2enable as primary apple id.png

2.2 创建用于后台生成client_secret的私钥

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第3张图片

2.2.1key.png

填写KeyName,勾选Sign In With Apple,点击Configure

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第4张图片

2.2.2填写key信息.png

选择对应的App ID,点击Save

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第5张图片

2.2.3配置私有key.png

选择continue

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第6张图片

2.2.4选择continue

注册

 

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第7张图片

2.2.5注册

点击Download,下载私钥,只能下载一次

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第8张图片

2.2.6下载

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第9张图片

2.2.7.保留keyid

你也可以返回Keys列表,点击刚才我们创建的Key,查看Key ID,复制保存,以备用

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第10张图片

2.2.8.查看keyid.png

2.3. Xcode 开启Sign In With Apple

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第11张图片

2.3.Xcode添加Sign in With Apple.png

3.接入大致时序图

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第12张图片

一张不规范的Apple登录时序图.png

其实我们校验可以有两种方式:
方案一:是我们App自己校验,解析SDK返回的userid、identityToken,然后将userId给后台,后台进行数据库查询,返回绑定信息。这种比较简单。
方案二:是App拿到SDK返回的authorization_code,交给后台,后台通过https://appleid.apple.com/auth/token接口检验,返回id_token,解析token并返回绑定信息。这种相对复杂

4.iOS端工作

授权大致需要以下几步:

  1. 导入授权库#import
  2. 添加响应事件,唤起Apple授权界面
  3. 验证密码或者生物ID
  4. 校验通过,将回调结果传递给后台

首先,添加事件按钮:

 

- (void)createAppleIDLoginButton{
    if (@available(iOS 13.0, *)) {
        self.appleLoginButton = [[ASAuthorizationAppleIDButton alloc] initWithAuthorizationButtonType:ASAuthorizationAppleIDButtonTypeSignIn authorizationButtonStyle:ASAuthorizationAppleIDButtonStyleWhiteOutline];
        [self.appleLoginButton addTarget:self action:@selector(signinWithApple) forControlEvents:UIControlEventTouchUpInside];
        [loginButtonView addSubview:self.appleLoginButton];
    }
}

// 唤起苹果登录
- (void)signinWithApple API_AVAILABLE(ios(13.0)){
    // 创建登录请求
    ASAuthorizationAppleIDRequest *idRequest = [[[ASAuthorizationAppleIDProvider alloc] init] createRequest];
    // 创建iCloud 密码填充登录,可不创建
    ASAuthorizationPasswordRequest *passwordRequest = [[[ASAuthorizationPasswordProvider alloc] init] createRequest];
    // 请求的用户数据
    idRequest.requestedScopes = @[ASAuthorizationScopeFullName,ASAuthorizationScopeEmail];
    // 如果不需要iCloud密码登录,不添加passwordRequest
    ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[idRequest,passwordRequest]];
    controller.delegate = self;
    controller.presentationContextProvider = self;
    [controller performRequests];
}

- (void)handleAppleResponse:(ASAuthorizationAppleIDCredential *)credential API_AVAILABLE(ios(13.0)){
    // 将返回的数据,提交给后台
}

其次,遵循代理ASAuthorizationControllerDelegate,ASAuthorizationControllerPresentationContextProviding,并实现代理回调:

 

#pragma mark - ASAuthorizationControllerDelegate
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]])       {
        ASAuthorizationAppleIDCredential *credential = authorization.credential;
        
        NSString *state = credential.state;
        NSString *user = credential.user;
        NSPersonNameComponents *fullName = credential.fullName;
        NSString *email = credential.email;
        NSString *authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding]; // refresh token
        NSString *identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding]; // access token
        ASUserDetectionStatus realUserStatus = credential.realUserStatus;
        
        Log(@"state: %@", state);
        Log(@"user: %@", user);
        Log(@"fullName: %@", fullName);
        Log(@"email: %@", email);
        Log(@"authorizationCode: %@", authorizationCode);
        Log(@"identityToken: %@", identityToken);
        Log(@"realUserStatus: %@", @(realUserStatus));

        // 数据处理
        [self handleAppleResponse:credential];
    }
  //else if([authorization.credential isKindOfClass:ASPasswordCredential.class]) {
        //ASPasswordCredential *credential = authorization.credential;
        //Log(@"user:%@", credential.user);
        //Log(@"password:%@", credential.password);
    //}
}

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
    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;
    }
    Log(@"%@", errorMsg);
}

#pragma mark - ASAuthorizationControllerPresentationContextProviding
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
    return self.window;
}

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第13张图片

首次唤起界面.PNG

苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走_第14张图片

授权后再次唤起界面.PNG

5.后台工作

接收iOS端授权结果并验证,向苹果服务请求id_token,大致流程为:

5.1. 提供一个接口接收user(唯一标识)、identityToken、authorizationCode,并进行签名验证;

使用苹果给出的Json Web Key,生成公钥,对客户端传过来的identityToken进行验签。

公钥地址:https://appleid.apple.com/auth/keys, 需要我们转化为公钥,公钥转换参考地址:https://8gwifi.org/jwkconvertfunctions.jsp

 

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "AIDOPK1",
      "use": "sig",
      "alg": "RS256",
      "n": "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w",
      "e": "AQAB"
    }
  ]
}

要验证身份令牌,服务器必须:
使用服务器的公钥验证JWS E256签名
验证随机数以进行身份验证(authorizationcode)
验证iss字段包含https://appleid.apple.com
确认aud栏位是开发人员的client_id(bundle id)
验证时间早于令牌的exp值

5.2. 生成苹果服务器验证合法性所需JWT格式的client_secret;
client_secret生成所需参数:
privatekey:苹果开发者账号中创建,只能下载一次(.P8格式)需要妥善保管
alg:算法"ES256"
kid:私钥id,苹果开发者账号中创建的私钥对应的key_id
iss:team_id,苹果开发者账号中获取 Team ID
iat:密钥开始时间,UTC秒
exp:密钥到期时间,其值不得大于服务器上的当前Unix时间的15777000(6个月,以秒为单位)
aud:固定值"https://appleid.apple.com"
sub:bundle id

创建令牌后,使用带有P-256曲线的椭圆曲线数字签名算法(ECDSA)和SHA-256哈希算法对其进行签名。在算法标题键中指定值ES256。在kid属性中指定密钥标识符。

5.3. 对苹果后台通过https://appleid.apple.com/auth/token接口返回的数据和客户端通过接口传过来的数据进行校验

您最多可以每天验证一次刷新令牌,以确认该设备上用户的Apple ID在Apple的服务器上仍然保持良好的信誉。如果您尝试每天多次验证用户的Apple ID,Apple的服务器可能会限制您的通话。

You may verify the refresh token up to once a day to confirm that the user’s Apple ID on that device is still in good standing with Apple’s servers. Apple’s servers may throttle your call if you attempt to verify a user’s Apple ID more than once a day.

参数:
client_id:客户端bundle id字符串,授权、刷新token公用,必传。
client_secret:上一步生成的JWT签名字符串,授权、刷新token公用,必传。
code:客户端传过来的 authorizationcode,授权专用,一次性使用,有效期5分钟。
grant_type:客户端与服务器交互类型,固定字符串, 授权、刷新token公用,必传。授权传“authorization_code”,刷新Token用“refresh_token”。
refresh_token:刷新Token使用的令牌,刷新token时用。
redirect_uri:授权时用的重定向URL,web使用AppleID登录时使用。如果是使用web,开发者网站中配置sign in with appleid 时填写Web Authentication Configuration,原生可不传。

苹果后台返回的数据:

 

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

//示例:
{

    "access_token": "ac6dd62539f5441cdacd7b548a9fe33a9.0.nrszq.gCD9GEmcznYjt5m3h4UkEg",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "r9f10xxxxxxxxxxxe80e.0.nxxxxq.fk7Q1ZxxxxxxxxxM0w",
    "id_token": "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnpvZnVuZC5xaWFuZ3VuZ3VuIiwiZXhwIjoxNTgzMTMxNzI1LCJpYXQiOjE1ODMxMzExMjUsInN1YiI6IjAwMTI5MC44MDYzZGRmODMwYjI0YTQ5OTc4OTZhNmUxOGNmMjE5Yi4xMDEzIiwiYXRfaGFzaCI6IjBrU05fMzlkcGxhUEdnMUd0YV9Ka1EiLCJlbWFpbCI6InJlZXM5cGd3NWJAcHJpdmF0ZXJlbGF5LmFwcGxlaWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiaXNfcHJpdmF0ZV9lbWFpbCI6InRydWUiLCJhdXRoX3RpbWUiOjE1ODMxMzEwOTV9.p19sc-tjsNQNCXyb33AX9r4oXj1xA4EmKh9Yp5E5ImxnOe6n_ISqvgMyDGqOuZwLAP9iMfB4-S_--1dpuzPx4HtwOyygpHhZSEZ4GcynCpHg6MFC7Mlkcn34J_awEXPeox_nJMRPRMN-ydQ7GxLvSrEJPJ-1rL473pIBc-DyNdYjkXcuyVU4FN6nEuh2NrOKCzMjkeEDqSmL2nG_TM7qE7JscAOcI6Nv5oml2KkYMeQl24kopQa2rC3m8HSsYSdaPs04pdiFEF20Fl3RqR-cnE0UeTmlC4KaBRF4xGpPpNT-OKvW2P6yUrkHmS27Mt1vM1sJkCiKMUGO3_i0Ef7ghA"
}

对id_token中的header、payload进行base64解码,获取相应的信息:

 

header: 
{
"kid":"eXaunmL",
"alg":"RS256"
} 
payload:
{
"iss":"https://appleid.apple.com",
"aud":"com.qiangungun",
"exp":1582880575,
"iat":1582879975,
"sub":"这个字段和客户端拿到的user以及identityToken第二段base64解码出来的sub相同",
"at_hash":"8_moKGpSUFG-zueTcf5EjQ",
"email":"[email protected]",
"email_verified":"true",
"is_private_email":"true",
"auth_time":1582879944
}

通过对客户端传过来的user与payload中的sub进行对比,可以确定是否是统一用户

5.4. 校验通过,返回绑定状态或者注册状态

如果已经绑定过,返回sessionId、userId
如果未绑定过,返回类似于微信登录所需要信息,客户端进行绑定操作



作者:yuyangkk
链接:https://www.jianshu.com/p/12aade4d053b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

你可能感兴趣的:(苹果登陆代理方法didCompleteWithAuthorization没有调用,didCompleteWithError没有走)