title: ios-苹果登录
categories: Ios
tags: [ios, signin, 登录]
date: 2021-05-10 17:52:14
comments: false
mathjax: true
toc: true
ios-苹果登录
前篇
- 官方
- Authentication Services - https://developer.apple.com/documentation/authenticationservices
- Generate and Validate Tokens - https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
- iOS 苹果授权登录(Sign in with Apple) - https://www.wangquanwei.com/560.html
- 苹果推出了 Sign in with Apple 功能 - https://www.cnblogs.com/ljcgood66/p/12537389.html
客户端接入
-
在 singing & capabilities 中加入 sign in with apple (不勾选会出现 认证错误
-
登录代码
-
实现回调
ASAuthorizationControllerDelegate
,ASAuthorizationControllerPresentationContextProviding
@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
-
登录返回的数据
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
-
-
未登录过的情况下
-(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"]; } }
-
已登录过的情况下
// 如果存在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"]; } }
-
检测已存在的 userId 是否合法
// 使用 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); } }
-
服务器校验
- Sign in With Apple之服务端验证 - https://www.jianshu.com/p/8646f599c627
- golang 实例代码: https://github.com/pyihe/apple_validator
根据上面可以得出验证 IdentityToken
的步骤为:
- 以
.
为分隔点, 将IdentityToken分隔为三部分, 第三部分为签名, 留着用于验证 - 使用Base64URL解码对应的Header和Payload, 并JSON反序列化为对应的结构体(或者键值对), 并且对Payload中相应对值进行验证,如exp, sub, iat, aud
- 通过 接口 从Apple Server获取RSA公钥,接口地址 https://appleid.apple.com/auth/keys, 这里需要注意, 获取到的结果通常为两个,需要用选择与Header中的
kid
值匹配的那个Key - 步骤3返回的Key中包含了RSA公钥中的
N
和E
的值,同样是用Base64URL编码后的值, 需要解码, 然后再构造RSA公钥 - 得到公钥后,将步骤1中得到的Base64URL编码的Header和Payload再次拼接起来,然后调用rsa.VerifyPKCS1v15()方法进行签名验证, 注意这里的Hash类型为
SHA256
踩坑
认证错误
错误: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000
解决办法: 在 singing & capabilities 中加入 sign in with apple 即可.