暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Sign In With Apple看这篇就够了

洛杉矶小前端23 2019-12-21
1218

“  我是如此的相信,在背后支撑的是你,一直与我并肩而行,仰望等太阳升起,听见鸟群回来的声音  

                                                                       ——我是如此的相信



为什么写这篇文章呢?主要出于以下两点考虑:

  • 首次上架的APP,如果不接入SIWA,抱歉,拒绝上架;

  • google到的文章质量不高、高度重复,甚至没有一篇可以直接拿来复现的;

于是借助项目的机会来实战,并把完整的过程分享给需要的工程师们。



01

SIWA简介


SIWA是iOS13新推出的一项服务,如果从前你的APP需要微信登录、账号密码等各种登录方式,那么以后使用果爹的SIWA便可以直接登录。虽然这里面也果爹自己的私心,但不得不说确实对用户体验和安全有很大提升。如果你的APP首次上架,强制接入SIWA服务。如果是APP更新,不强制,但这件事逃不开的。
既然要做这件事,那么了解它的原理或者流程是一件很有必要的事情。下图是一张时序图,能够看到你的应用、apple、你的后端三者之间的通信。

时序图




02


开始前准备


  • 环境准备:Xcode 11 、 Mac OS X 10.15 + 、iOS 13.0 +

Xcode具体用什么版本需要结合iOS版本来使用。比如Xcode11 Beta版本就不支持iOS13.3.导致编译成功后无法在设备上运行。优先下载正式版的Xcode即可

  • 在开发者后台打开SIWA权限并重新生成profile文件导入Xcode
  • Xcode中打开SIWA
  • 前端代码接入 & 后端验证代码接入


03


实战


SIWA实施分为三个步骤:配置权限、前端代码接入、后端验证。

1、配置权限

权限配置涉及开发者后台和Xcode工具。

开发者后台:进入Certificates, Identifiers & Profiles -> 选择 Identifiers -> 勾选SIWA选项


 开发者后台配置示意图


Xcode工具:Xcode选择对应的Target -> select 'signing & capabilities' -> 双击SIWA添加能力

Xcode配置示意图


2、前端代码接入

代码集成流程如下:

页面嵌入按钮

按钮是苹果提供的,允许做一些样式调整。该按钮要求在登录页面的显眼位置。

    if (@available(iOS 13.0, *)) {
    // Sign In With Apple Button
    ASAuthorizationAppleIDButton *appleLoginBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
    appleLoginBtn.frame = CGRectMake(30, 30, 200, 44);
    appleLoginBtn.cornerRadius = 22.f;
    [appleLoginBtn addTarget:self action:@selector(appleLoginBtnDidClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:appleLoginBtn];
    }

    点击按钮后触发登录验证请求

      - (void)appleLoginBtnDidClicked:(id)sender
      {
      if (@available(iOS 13.0, *)) {
      ASAuthorizationAppleIDRequest *appleIDRequest = [[ASAuthorizationAppleIDProvider new] createRequest];
      appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
      ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];

      // 设置代理
      authorizationController.delegate = self;
      authorizationController.presentationContextProvider = self;

      // 发起请求
      [authorizationController performRequests];
      }
      }

      发送请求的同时需要实现一些代理方法:

      • apple登录弹窗的载体设置

          - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0))
          {
          return self.view.window; // or [UIApplication sharedApplication].windows.lastObject;
          }
        • 请求成功回调

            - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
            {
            if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
            // 用户使用AppleId登录
            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;

            // 这里调自己的后端服务,由后端跟apple后台服务通信,并返回项目自身的用户数据(如果账号不存在,则新建一个返回)
            }
            }
          • 请求失败回调

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

              // do what you want to do here
              }


            以上就是基础版前端工作,如果想支持钥匙串记录密码并使用密码登录的话,还需要做一些额外操作。


            支持钥匙串需要做三点:导入Security.framework(无则加勉)、请求时追加钥匙串选项、请求回调中追加钥匙串处理。


            请求时追加钥匙串选项

              - (void)appleLoginBtnDidClicked:(id)sender
              {
              if (@available(iOS 13.0, *)) {
              ASAuthorizationAppleIDRequest *request = [[ASAuthorizationAppleIDProvider new] createRequest];
              [request setRequestedScopes:@[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]];


              ASAuthorizationController *appleSignController;


              // if 新用户登陆 (keychain 中未记录用户账户信息)
              appleSignController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
              // else 旧用户登陆,并且 keychain 中保存了用户的账户信息,可以同时使用 appleId登录 和 钥匙串登录
              ASAuthorizationPasswordRequest *passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];
              appleSignController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request, passwordRequest]];
              // end if


              // 设置代理
              appleSignController.delegate = self;
              appleSignController.presentationContextProvider = self;

              // 发起请求
              [appleSignController performRequests];
              }
              }


              请求回调中追加钥匙串处理

                - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
                {
                if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
                // 用户使用appleId登录
                // do everything like before
                // ...

                // 把账号信息存入钥匙串
                } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
                // 用户使用钥匙串登录
                ASPasswordCredential *passwordCredential = authorization.credential;
                NSString *user = passwordCredential.user;
                NSString *password = passwordCredential.password;

                // 不再请求apple服务验证,直接使用账号信息登录。
                }
                }


                3、后端验证

                前端在请求成功回调中,将拿到identityTokenauthorizationCode传给后端,

                后端携带这些数据与apple服务器通信请求验证和公钥

                验证成功后返回项目内部的账号给前端登录。具体的事情后端老哥可以的,相信他!



                04


                最后


                考虑到用户可能会在使用SIWA登录后,可能在设置中将其解绑或者切换其他AppleId账号,那么APP需要引起用户重新登录。

                设置路径如下:设置 -> AppleId -> 密码与安全性 -> 使用您AppleId的App -> 选择App -> 停止使用AppleID


                使用AppleID的App


                解绑

                用户解绑时,APP可能正在后台运行,也可能被kill了。因此,需要在应用启动和进入前台时检测凭证状态credentialState,并依据实际状态作对应处理。

                应用启动检查:

                  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

                  if (@available(iOS 13.0, *)) {
                  NSString *userIdentifier = 钥匙串中取出的 userIdentifier;
                  if (userIdentifier) {
                  ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
                  [appleIDProvider getCredentialStateForUserID:userIdentifier
                  completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState,
                  NSError * _Nullable error)
                  {
                  switch (credentialState) {
                  case ASAuthorizationAppleIDProviderCredentialAuthorized:
                  // do nothing
                  break;
                  case ASAuthorizationAppleIDProviderCredentialRevoked:
                  // AppleId遭解绑,引导用户重新登录
                  break;
                  case ASAuthorizationAppleIDProviderCredentialNotFound:
                  // 引导用户重新登录
                  break;
                  }
                  }];
                  }
                  }

                  return YES;
                  }

                  应用进入前台检查:

                    if (@available(iOS 13.0, *)) {
                    [[NSNotificationCenter defaultCenter] addObserver:self
                    selector:@selector(handleSignInWithAppleStateChanged:)
                    name:ASAuthorizationAppleIDProviderCredentialRevokedNotification
                    object:nil];
                    }
                    - (void)handleSignInWithAppleStateChanged:(NSNotification *)notification
                    {
                    // 引导用户重新登录
                    }



                    05


                    参考文献


                    https://arctouch.com/blog/how-to-integrate-sign-in-with-apple/

                    文章转载自洛杉矶小前端23,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                    评论