ios-苹果登录

ios-苹果登录


前篇


客户端接入

  1. singing & capabilities 中加入 sign in with apple (不勾选会出现 认证错误

  2. 登录代码

    1. 实现回调 ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      @interface AppleHelper()<ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
      @end

      @implementation AppleHelper
      // ------------------------------- 实现 ASAuthorizationControllerDelegate 的接口
      - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){

      if (@available(iOS 13.0, *)) {
      if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
      // 用户登录使用 ASAuthorizationAppleIDCredential
      ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;

      NSString *userId = appleIDCredential.user;
      NSString *familyName = appleIDCredential.fullName.familyName;
      NSString *givenName = appleIDCredential.fullName.givenName;
      NSString *email = appleIDCredential.email;
      NSString* extB = [NSString stringWithFormat:@"%s|%s|%s", email, familyName, givenName];

      // 服务器验证需要使用的参数
      NSString *identityTokenStr = [[NSString alloc] initWithData:appleIDCredential.identityToken encoding:NSUTF8StringEncoding];
      NSString *authorizationCodeStr = [[NSString alloc] initWithData:appleIDCredential.authorizationCode encoding:NSUTF8StringEncoding];

      CThirdLogin* tl = [CThirdLogin new];
      tl.userId = userId;
      tl.token = identityTokenStr;
      tl.extA = authorizationCodeStr;
      tl.extB = extB;
      [self doCallback:ECodeOk msg:[tl yy_modelToJSONString]];
      } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
      // 用户登录使用现有的密码凭证
      ASPasswordCredential *passwordCredential = authorization.credential;

      NSString *userId = passwordCredential.user;
      NSString *password = passwordCredential.password;

      CThirdLogin* tl = [CThirdLogin new];
      tl.userId = userId;
      tl.token = password;
      [self doCallback:ECodeOk msg:[tl yy_modelToJSONString]];
      } else {
      [self doCallback:ECodeUnknown msg:@"--- didCompleteWithAuthorization"];
      }
      } else {
      [self doCallback:ECodeSupportError msg:@"--- didCompleteWithAuthorization"];
      }
      }

      - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
      [self doCallback:ECodeLoginError msg:[NSString stringWithFormat:@"%@", error]];
      }

      // ------------------------------- 实现 ASAuthorizationControllerPresentationContextProviding 的接口
      - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)){
      return [Tool getRootViewCtrl].view.window;
      }

      @end
      • 登录返回的数据

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
                2021-05-10 18:04:09.904086+0800 MyIosTest[271:4447] --- userId: 001104.1f7794b73e074767bd31e4173fc5fd8e.1004
        2021-05-10 18:04:09.904172+0800 MyIosTest[271:4447] --- familyName: Mehak, givenName: Shivani, nickname: (null)
        2021-05-10 18:04:09.904208+0800 MyIosTest[271:4447] --- email: htj2n7ks5z@privaterelay.appleid.com
        2021-05-10 18:04:09.904247+0800 MyIosTest[271:4447] --- identityTokenStr: eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnJtZ3J1bW15aW9zLnBybyIsImV4cCI6MTYyMDcyNzQ0OSwiaWF0IjoxNjIwNjQxMDQ5LCJzdWIiOiIwMDExMDQuMWY3Nzk0YjczZTA3NDc2N2JkMzFlNDE3M2ZjNWZkOGUuMTAwNCIsImNfaGFzaCI6IjZEQmNwakNhbGZFXy1GS2o5Q3diaGciLCJlbWFpbCI6Imh0ajJuN2tzNXpAcHJpdmF0ZXJlbGF5LmFwcGxlaWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiaXNfcHJpdmF0ZV9lbWFpbCI6InRydWUiLCJhdXRoX3RpbWUiOjE2MjA2NDEwNDksIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.1IQXo9mo9OPI5TSf0gLuXj1YTKETFBBGZtCgQD9GKTTKHztj9nhjHHBCziDLl__TKc4JSxAl5mUZO-nhrnsuU5Q8dGAbgQwDle5uGeBQ8NTw00jSr-JpjVOZ-MuMnWR1wygzgQGyQV20fGpQ9g37U6KsXVGoPg32KfluEZmVygUzD8IOy27NolP3XtC4FwYn7IUDDseeTN1_8tCbf3PUOdm8jHzVN08asGzP_jmJVrPZV-Dr3wCDoTgwDfkeVBr8_pdF3-tq955TznRy_voiaw56x-V-ZNF3OUN826C2D9wtg9pY4cTV8jxj2YGoIfZgPx-dX-iVXgkT0E4DP6vs5A
        2021-05-10 18:04:09.905094+0800 MyIosTest[271:4447] --- authorizationCodeStr: c8128abb30c714a7aafc9a1d55e702fac.0.mrrqu.jZtUOrps6JJpugTzrUU-Bw

        2. 未登录过的情况下

        ```objective-c
        -(void)login:(CodeIdFn)cb {
        self.cb = cb;

        if (@available(iOS 13.0, *)) {
        ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
        ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
        request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
        ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
        controller.delegate = self;
        controller.presentationContextProvider = self;
        [controller performRequests];
        } else {
        [self doCallback:ECodeSupportError msg:@"--- login"];
        }
        }
    2. 已登录过的情况下

      ```objective-c
      // 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户
      -(void)perfomExistingAccount:(CodeIdFn)cb {

       self.cb = cb;
       
       if (@available(iOS 13.0, *)) {
           ASAuthorizationAppleIDRequest *appleIDRequest = [[ASAuthorizationAppleIDProvider new] createRequest];
           ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];
           ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
           controller.delegate = self;
           controller.presentationContextProvider = self;
           [controller performRequests];
       } else {
          [self doCallback:ECodeSupportError msg:@"--- perfomExistingAccount"];
      }
      

      }

    3. 检测已存在的 userId 是否合法

      ```objective-c
      // 使用 userId 查看是否有合法的登录用户
      -(void)checkAuth:(NSString*)userId completeHandler:(BoolFn)completeHandler {

       if (userId == nil || userId.length <= 0) {
           completeHandler(NO);
           return;
       }
      
       if (@available(iOS 13.0, *)) {
           ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
           [provider getCredentialStateForUserID:userId completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
               BOOL authorized = NO;
               switch (credentialState) {
                   case ASAuthorizationAppleIDProviderCredentialAuthorized:
                       authorized = YES;
                       break;
                   default:
                       authorized = NO;
                       break;
               }
               completeHandler(authorized);
           }];
       } else {
           completeHandler(NO);
       }
      

      }


服务器校验

根据上面可以得出验证 IdentityToken 的步骤为:

  1. .为分隔点, 将IdentityToken分隔为三部分, 第三部分为签名, 留着用于验证
  2. 使用Base64URL解码对应的Header和Payload, 并JSON反序列化为对应的结构体(或者键值对), 并且对Payload中相应对值进行验证,如exp, sub, iat, aud
  3. 通过 接口 从Apple Server获取RSA公钥,接口地址 https://appleid.apple.com/auth/keys, 这里需要注意, 获取到的结果通常为两个,需要用选择与Header中的kid值匹配的那个Key
  4. 步骤3返回的Key中包含了RSA公钥中的NE的值,同样是用Base64URL编码后的值, 需要解码, 然后再构造RSA公钥
  5. 得到公钥后,将步骤1中得到的Base64URL编码的Header和Payload再次拼接起来,然后调用rsa.VerifyPKCS1v15()方法进行签名验证, 注意这里的Hash类型为SHA256

踩坑

认证错误

错误: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000

解决办法: 在 singing & capabilities 中加入 sign in with apple 即可.