iOS 13-Sign In with Apple
最近了解了iOS 13
新增功能之Sign In with Apple
,Sign In with Apple
是跨平台的,可以支持iOS、macOS、watchOS、tvOS、JS
。本文主要内容为Sign In with Apple
在iOS
上的基础使用。详情参考WWDC 2019
- 审核备注
Sign In with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.
也就是说,当 Sign In with Apple 服务正式上线以后,所有已接入其它第三方登录的 App,Sign In with Apple 将被要求作为一种登录选择,否则有可能就不给过。详情参考App Store审核指南更新
-
开发
Sign In with Apple
的注意事项
需要在苹果后台打开该选项,并且重新生成Profiles
配置文件,并安装到Xcode
,如下图 -
iOS
使用Sign In with Apple
在Xcode
的准备工作
在Xcode11 Signing & Capabilities
中添加Sign In With Apple
,如下图 iOS Sign In with Apple
流程
- 添加 Sign In with Apple 登录按钮,设置 ASAuthorizationAppleIDButton 相关布局,并添加按钮点击响应事件
- 获取授权码
- 验证
- 添加
Sign In with Apple
登录按钮,设置ASAuthorizationAppleIDButton
相关布局,并添加按钮点击响应事件。当然苹果也允许自定义苹果登录按钮的样式,样式要求详见这个文档:Human Interface Guidelines
- (void)configUI{
// 用于展示Sign In With Apple 登录过程的信息
_appleIDInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 40.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) * 0.4)];
_appleIDInfoLabel.font = [UIFont systemFontOfSize:22.0];
_appleIDInfoLabel.numberOfLines = 0;
_appleIDInfoLabel.lineBreakMode = NSLineBreakByWordWrapping;
_appleIDInfoLabel.text = @"显示Sign In With Apple 登录信息\n";
[self.view addSubview:_appleIDInfoLabel];
if (@available(iOS 13.0, *)) {
// Sign In With Apple Button
ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180, self.view.bounds.size.width - 60, 100);
// appleBtn.cornerRadius = 22.f;
[appleIDBtn addTarget:self action:@selector(handleAuthorizationAppleIDButtonPress) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:appleIDBtn];
}
}
// 处理授权
- (void)handleAuthorizationAppleIDButtonPress{
NSLog(@"////////");
if (@available(iOS 13.0, *)) {
// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 创建新的AppleID 授权请求
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 在用户授权期间请求的联系信息
appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
// 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
// 设置授权控制器通知授权请求的成功与失败的代理
authorizationController.delegate = self;
// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
authorizationController.presentationContextProvider = self;
// 在控制器初始化期间启动授权流
[authorizationController performRequests];
}
}
- 已经使用
Sign In with Apple
登录过app
的用户
如果设备中存在iCloud Keychain
凭证或者AppleID
凭证,提示用户直接使用TouchID
或FaceID
登录即可,代码如下
// 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户
- (void)perfomExistingAccountSetupFlows{
NSLog(@"///已经认证过了/////");
if (@available(iOS 13.0, *)) {
// 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
// 授权请求AppleID
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
// 为了执行钥匙串凭证分享生成请求的一种机制
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
// 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
// 设置授权控制器通知授权请求的成功与失败的代理
authorizationController.delegate = self;
// 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
authorizationController.presentationContextProvider = self;
// 在控制器初始化期间启动授权流
[authorizationController performRequests];
}
}
- 获取授权码
获取授权码需要在代码中实现两个代理回调ASAuthorizationControllerDelegate、ASAuthorizationControllerPresentationContextProviding
分别用于处理授权登录成功和失败、以及提供用于展示授权页面的Window
,代码如下
#pragma mark - delegate
//@optional 授权成功地回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization{
NSLog(@"授权完成:::%@", authorization.credential);
NSLog(@"%s", __FUNCTION__);
NSLog(@"%@", controller);
NSLog(@"%@", authorization);
// 测试配置UI显示
NSMutableString *mStr = [NSMutableString string];
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
// 用户登录使用ASAuthorizationAppleIDCredential
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;
// Create an account in your system.
// For the purpose of this demo app, store the userIdentifier in the keychain.
// 需要使用钥匙串的方式保存用户的唯一信息
[YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];
[mStr appendString:user];
[mStr appendString:@"\n"];
[mStr appendString:familyName];
[mStr appendString:@"\n"];
[mStr appendString:givenName];
[mStr appendString:@"\n"];
[mStr appendString:email];
NSLog(@"mStr:::%@", mStr);
[mStr appendString:@"\n"];
_appleIDInfoLabel.text = mStr;
}else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]){
// Sign in using an existing iCloud Keychain credential.
// 用户登录使用现有的密码凭证
ASPasswordCredential *passwordCredential = authorization.credential;
// 密码凭证对象的用户标识 用户的唯一标识
NSString *user = passwordCredential.user;
// 密码凭证对象的密码
NSString *password = passwordCredential.password;
[mStr appendString:user];
[mStr appendString:@"\n"];
[mStr appendString:password];
[mStr appendString:@"\n"];
NSLog(@"mStr:::%@", mStr);
_appleIDInfoLabel.text = mStr;
}else{
NSLog(@"授权信息均不符");
mStr = [@"授权信息均不符" copy];
_appleIDInfoLabel.text = mStr;
}
}
// 授权失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error{
// Handle error.
NSLog(@"Handle error:%@", error);
NSString *errorMsg = nil;
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;
}
NSMutableString *mStr = [_appleIDInfoLabel.text mutableCopy];
[mStr appendString:@"\n"];
[mStr appendString:errorMsg];
[mStr appendString:@"\n"];
_appleIDInfoLabel.text = mStr;
}
// 告诉代理应该在哪个window 展示内容给用户
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller{
NSLog(@"88888888888");
// 返回window
return self.view.window;
}
在授权登录成功回调中,我们可以拿到以下几类数据
- UserID:
Unique, stable, team-scoped user ID
,苹果用户唯一标识符,该值在同一个开发者账号下的所有App
下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来(这与国内的微信、QQ、微博
等第三方登录流程基本一致) - Verification data:
Identity token, code
,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证,本次授权登录请求数据的有效性和真实性,详见Sign In with Apple REST API - Account information:
Name, verified email
,苹果用户信息,包括全名、邮箱等,注意:如果玩家登录时拒绝提供真实的邮箱账号,苹果会生成虚拟的邮箱账号
- 验证
关于验证的这一步,需要传递授权码给自己的服务端,自己的服务端调用苹果API
去校验授权码Generate and validate tokens。如果验证成功,可以根据userIdentifier
判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录状态给App
附:官方示例代码 Swift 版
附:What the Heck is Sign In with Apple?
附:苹果授权登陆后端验证
附:App Store审核指南的更新
附:Generate and validate tokens
附:SignInAppleDemo
附:我的博客地址