ios-苹果登录
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
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
242021-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"];
}
}
已登录过的情况下
```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"]; }
}
检测已存在的 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); }
}
服务器校验
- 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 即可.