【环境】Xcode 12.0.1,macOS 10.15.7
一、ViewController 代码
1、创建一个新工程,取名为 SignInWithApple,设置为自动签名
2、为其添加 Sign in with Apple Capability
3、打开 ViewController.h,导入
#import <AuthenticationServices/AuthenticationServices.h>
4、先创建一个 UIButton,或者使用 AuthenticationService 里自带的 ASAuthorizationAppleIDButton,注意,这个 button 仅仅包含UI,点击事件逻辑还是得自己添加。
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setBackgroundColor:[UIColor blueColor]];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[button setTitle:@"Login" forState:UIControlStateNormal];
button.frame = CGRectMake(0, 0, 40 * 1.73, 40);
button.center = self.view.center;
[button addTarget:self action:@selector(onLoginClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
或者
ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton
buttonWithType:ASAuthorizationAppleIDButtonTypeSignUp
style:ASAuthorizationAppleIDButtonStyleBlack];
button.center = self.view.center;
[button addTarget:self action:@selector(onLoginClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
5、创建对应的点击方法
- (void)onLoginClick {
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc] init];
ASAuthorizationAppleIDRequest *request = [provider createRequest];
// 获取 用户名 和 Email(同一 BundleId 首次使用 Sign in with Apple 才有对应的值返回)
request.requestedScopes = @[
ASAuthorizationScopeFullName,
ASAuthorizationScopeEmail,
];
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
}
6、实现代理 ASAuthorizationControllerDelegate
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
// 鉴权成功
// 将登录凭证保存到钥匙串中
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 如果是 AppleID 凭证,保存
ASAuthorizationAppleIDCredential *credential = (ASAuthorizationAppleIDCredential *)authorization.credential;
// email 和 fullName,仅在同一 BundleId 第一次使用 Sign in with Apple 时才有值
NSLog(@"user = %@", credential.user);
NSLog(@"state = %@", credential.state);
NSLog(@"authorizedScopes = %@", credential.authorizedScopes);
NSLog(@"authorizationCode = %@", credential.authorizationCode);
NSLog(@"identityToken = %@", credential.identityToken);
NSLog(@"email = %@", credential.email);
NSLog(@"namePrefix = %@", credential.fullName.namePrefix);
NSLog(@"givenName = %@", credential.fullName.givenName);
NSLog(@"middleName = %@", credential.fullName.middleName);
NSLog(@"familyName = %@", credential.fullName.familyName);
NSLog(@"nameSuffix = %@", credential.fullName.nameSuffix);
NSLog(@"nickname = %@", credential.fullName.nickname);
NSLog(@"%ld", (long)credential.realUserStatus);
NSData *userData = [credential.user dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *attributes = @{
(NSString *)kSecClass: (NSString *)kSecClassGenericPassword,
// 单用户登录,账号可以写死
(NSString *)kSecAttrAccount: @"appAccount",
// 当 kSecClass = kSecClassGenericPassword 时,kSecValueData 里的内容会加密,更安全
(NSString *)kSecValueData: userData,
};
CFTypeRef result;
OSStatus res = SecItemAdd((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&result);
if (res == errSecSuccess) {
NSLog(@"success");
}
}
}
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
// 鉴权失败
NSLog(@"err: %@", error);
}
7、实现代理 ASAuthorizationControllerPresentationContextProviding
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
// 在哪个 Window 展示登录框
return UIApplication.sharedApplication.windows.firstObject;
}
二、AppDelegate 代码
1、在 AppDelegate.h 中添加
#import <AuthenticationServices/AuthenticationServices.h>
2、在 AppDelegate.m 中 didFinishLaunchingWithOptions 中添加
[[[ASAuthorizationAppleIDProvider alloc] init] getCredentialStateForUserID:[self getUserId] completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
// 查询当前账号是否在钥匙串中有记录了,不需要用户重复登录
switch (credentialState) {
case ASAuthorizationAppleIDProviderCredentialRevoked:
NSLog(@"ASAuthorizationAppleIDProviderCredentialRevoked");
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
NSLog(@"ASAuthorizationAppleIDProviderCredentialAuthorized");
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
NSLog(@"ASAuthorizationAppleIDProviderCredentialNotFound");
break;
case ASAuthorizationAppleIDProviderCredentialTransferred:
NSLog(@"ASAuthorizationAppleIDProviderCredentialTransferred");
break;
default:
break;
}
}];
其中 getUserId 是获取保存在钥匙串中的 userId
- (NSString *)getUserId {
// 单用户登录,直接写死账号,只需要查到 userId 即可
NSDictionary *query = @{
(NSString *)kSecClass: (NSString *)kSecClassGenericPassword,
(NSString *)kSecMatchLimit: (NSString *)kSecMatchLimitOne, // 只返回一个记录
(NSString *)kSecAttrAccount: @"appAccount", // 账号写死
(NSString *)kSecReturnData: @(YES), // 需要返回 Item 的 Data,数据保存在这里
// 返回对象的类型不同
// 值为 YES 时,attributes 和 data 放在同一个 CFDictionaryRef 中返回
// 值为 NO 时,单独返回 data,放在 CFDataRef 中返回
(NSString *)kSecReturnAttributes: @(YES),
};
CFTypeRef result;
OSStatus res = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (res == errSecSuccess) {
NSDictionary *resDic = (__bridge_transfer NSDictionary *)result;
NSData * data = resDic[(NSString *)kSecValueData];
NSString *user = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return user;
} else {
return nil;
}
}
苹果登录集成结束
参考文档
https://developer.apple.com/documentation/authenticationservices
Demo 地址
https://github.com/huangrrui/Sign-in-with-Apple