1. 启用 Sign in with Apple
选择项目 TARGETS -> Signing&Capabilities ,单击下图中的 3:Capability:
在弹出框中搜索找到 Sign in with Apple:
然后,双击,即可启用Apple 登录:
点击右侧的x,可以关闭 Apple 登录。
最后,登录开发者中心,在需要启用 Sign in with Apple 的 Apple ID中勾选 Sign in with Apple:
这些完成后,我们就可以在项目中使用相关的功能了。
2. 添加代码
在需要使用 login with Apple 的地方,引入头文件:
#import <AuthenticationServices/AuthenticationServices.h>
首先,我们需要一个登录按钮,系统为我们预设了一个固定样式登录按钮ASAuthorizationAppleIDButton
,我们可以这样使用它:
ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:(ASAuthorizationAppleIDButtonTypeSignIn) style:(ASAuthorizationAppleIDButtonStyleBlack)];
[button addTarget:self action:@selector(buttonAction:) forControlEvents:(UIControlEventTouchUpInside)];
button.frame = CGRectMake(60, 60, 200, 60);
[self.view addSubview:button];
这时,按钮的样式是这样的:
如果我们设置的按钮是正方形,或者宽度不足以显示文字,将会出现一个只有苹果logo的按钮:
通过参数 type、style可以设置为不同样式的按钮;当然,我们也可以自定义,但是要遵循苹果的相关设计规范,详见 Human Interface Guidelines。
接下来就是需要添加授权的相关代码了;
3. 申请授权
在申请授权的过程中使用到的类都比较简单:
//主要作用是用创建相应的请求,查询用户授权状态
ASAuthorizationAppleIDProvider
// 授权请求,可以设置具体的请求信息
ASAuthorizationAppleIDRequest
// 发送请求控制器,可以设置相应的协议
ASAuthorizationController
这其中使用到了两个协议: ASAuthorizationControllerDelegate
ASAuthorizationControllerPresentationContextProviding
,前者用于接收请求成功或者失败的回调;后者用于返回弹出请求框的window,一般返回当前视图window即可!
// ASAuthorizationControllerDelegate
// 成功的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization NS_SWIFT_NAME(authorizationController(controller:didCompleteWithAuthorization:));
// 失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error NS_SWIFT_NAME(authorizationController(controller:didCompleteWithError:));
// ASAuthorizationControllerPresentationContextProviding
// 返回弹出请求视图的window;
// ASPresentationAnchor 为 UIWindow 的别名:
// typedef UIWindow * ASPresentationAnchor;
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller;
4. 发起授权
//基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
// 创建请求
ASAuthorizationAppleIDRequest *req = [provider createRequest];
// 设置请求的信息
req.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
// 创建管理授权请求的控制器
ASAuthorizationController *controller = [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[req]];
// 设置代理
controller.delegate = self;
controller.presentationContextProvider = self;
// 发起请求
[controller performRequests];
然后实现相应的代理协议:
// 授权失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
NSString *msg = @"未知";
switch (error.code) {
case ASAuthorizationErrorCanceled:
msg = @"用户取消";
break;
case ASAuthorizationErrorFailed:
msg = @"授权请求失败";
break;
case ASAuthorizationErrorInvalidResponse:
msg = @"授权请求无响应";
break;
case ASAuthorizationErrorNotHandled:
msg = @"授权请求未处理";
break;
case ASAuthorizationErrorUnknown:
msg = @"授权失败,原因未知";
break;
default:
break;
}
NSLog(@"%@", msg);
}
// 授权成功的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
ASAuthorizationAppleIDCredential *credential = authorization.credential;
//苹果用户唯一标识符,
// 该值在同一个开发者账号下的所有 App 下是一样的,
//开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
NSString *user = credential.user;
// 获取用户名
NSString *familyName = credential.fullName.familyName;
NSString * givenName = credential.fullName.givenName;
// 获取邮箱
NSString *email = credential.email;
// 获取验证信息
// 验证数据,用于传给开发者后台服务器,
// 然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性,
// 如果验证成功,可以根据 userIdentifier 判断账号是否已存在,
// 若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录态给 App。
NSData *identityToken = credential.identityToken;
NSData *code = credential.authorizationCode;
if (self.completeHander) {
self.completeHander(YES, user, familyName, givenName, email, nil, identityToken, code, nil, @"授权成功");
}
} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
// 使用现有的密码凭证登录
ASPasswordCredential *credential = authorization.credential;
// 用户唯一标识符
NSString *user = credential.user;
NSString *password = credential.password;
if (self.completeHander) {
self.completeHander(YES, user, nil, nil, nil, password, nil, nil, nil, @"授权成功");
}
}
}
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
return [UIApplication sharedApplication].windows.firstObject;
}
点击登录后,如果是未授权会弹出如下弹框:
这里的邮件地址,用户可以选择共享或者隐藏,如果选择了隐藏,开发者将会获得一个苹果自动生成的一个邮箱地址,而不是用户的真实邮箱;成功后返回的信息如下:
user: 004673.27e48e27b0e5853a7c5d744d9a1c8725.0683
familyName: 刘
givenName: 流火绯瞳
email: c96fqxirwu@privaterelay.appleid.com
这里我选择了隐藏邮箱,返回的是苹果生成的一个邮箱地址;
我们可以将 user 信息保存到keychain中,
如果我们已经授权登录成功,再次登录的时候,就会显示如下的页面:
如果把登录的信息保存到Keychain,我们可以使用下面的方式进行读取密码登录:
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
ASAuthorizationAppleIDRequest *req = [provider createRequest];
ASAuthorizationPasswordProvider *pasProvider = [[ASAuthorizationPasswordProvider alloc]init];
ASAuthorizationPasswordRequest *pasReq = [pasProvider createRequest];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:2];
if (req) {
[arr addObject:req];
}
if (pasReq) {
[arr addObject:pasReq];
}
ASAuthorizationController *controller = [[ASAuthorizationController alloc]initWithAuthorizationRequests:arr.copy];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
这时获取到的信息将只有user:
user: 004673.27e48e27b0e5853a7c5d744d9a1c8725.0683
familyName: (null)
givenName: (null)
email: (null)
app登录成功后,需要将获取到的 identityToken
、code
等信息发送给后台,然后由后台调用 Apple 的后台API,来验证用户的真实性,从而完成验证,详情参考 Sign In With Apple 从登陆到服务器验证
5. 检测登录/授权状态
登录成功后,用户是可以随时取消授权的,或者用户将 AppleID退出了当前设备,都需要重新获取;
我们可以在应用启动的时候使用下面的方法来检测用户状态:
// 使用 user 信息,查询当前用户的状态
+ (void) checkAuthorizationStateWithUser:(NSString *) user
completeHandler:(void(^)(BOOL authorized, NSString *msg)) completeHandler {
if (user == nil || user.length <= 0) {
if (completeHandler) {
completeHandler(NO, @"用户标识符错误");
}
return;
}
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
[provider getCredentialStateForUserID:user completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
NSString *msg = @"未知";
BOOL authorized = NO;
switch (credentialState) {
case ASAuthorizationAppleIDProviderCredentialRevoked:
msg = @"授权被撤销";
authorized = NO;
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
msg = @"已授权";
authorized = YES;
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
msg = @"未查到授权信息";
authorized = NO;
break;
case ASAuthorizationAppleIDProviderCredentialTransferred:
msg = @"授权信息变动";
authorized = NO;
break;
default:
authorized = NO;
break;
}
if (completeHandler) {
completeHandler(authorized, msg);
}
}];
}
如果app在运行中,我们可以通过添加通知的方法来实时监控:
- (void) startAppleIDObserverWithCompleteHandler {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lq_signWithAppleIDStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
- (void) lq_signWithAppleIDStateChanged:(NSNotification *) noti {
NSLog(@"%@", noti.name);
NSLog(@"%@", noti.userInfo);
}
这里的 userInfo 信息一直都是 null,需要我们再次使用上面的方法去检测授权状态,然后做出相应的处理。
6. 取消授权
对应用授权登录后,我们可以在设备中取消对某个app的授权,操作方法如下:
设置 -> Apple ID -> 密码与安全性 -> 使用您 Apple ID 的 App
打开后会显示所有使用 Apple ID 进行登录的 App:
选择需要取消的app,停止使用 Apple ID 即可!
7. 一些问题
Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000 "
项目配置中未添加 Sign in with Apple
,参考文章第一步