Sign In with Apple 让用户可以使用面容 ID 或触控 ID 来轻松进行身份认证,而内置的双重认证则再增添一层安全保障
1.实现
实现分四大部分:
1、创建Sign in with Apple Button.
2、跟用户提出授权请求.
3、根据用户的授权来验证用户.
4、处理用户授权变更.
2.开启 Sign in with Apple 功能
登录开发者网站,在需要添加 Sign in with Apple 功能的 Identifier 开启功能。
1.登陆developer账号,在app bundle ID的Capabilities里,打勾Sign In with Apple
2.在Xcode(Xcode 11.0 Beta或更新版本)的工程里面 Signing & Capabilities 开启Sign in with Apple 功能。
3.代码集成
3.1创建登录按钮
官方提供了一个 ASAuthorizationAppleIDButton (继承自UIControl),使用这个来创建一个登录按钮。
Apple 提供的登录按钮有三种外观:白色,带有黑色轮廓线的白色和黑色。∙ 文案有两种:Sign In
with Apple 和 Continue with Apple。(具体使用哪个文案,根据自身业务需求来定)另外,按钮宽高默认值为 {width:130, height:30}。
对于 ASAuthorizationAppleIDButton 我们能够自定义的东西比较少,比如背景色不能更改,文案只有两种可选,并且值不能修改,可以调整的只有圆角cornerRadius和size 。
3.2Authorization 发起授权登录请求
- (void)signInWithApple API_AVAILABLE(ios(13.0))
{
ASAuthorizationAppleIDProvider *provider =[[ASAuthorizationAppleIDProvider alloc] init];
ASAuthorizationAppleIDRequest *request = [provider createRequest];
request.requestedScopes = @[ASAuthorizationScopeFullName,ASAuthorizationScopeEmail];
ASAuthorizationController *vc = [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[request]];
vc.delegate =self;// 会提供请求后的结果回调,比如用户请求失败,或者用户请求成功。
vc.presentationContextProvider = self;// 是为验证的用户界面提供所需的window
[vcperformRequests];
}
3.3授权回调处理来验证用户
下面是 ASAuthorizationControllerDelegate 方法,一个是授权成功的回调,一个是失败的回调。
#pragma mark - ASAuthorizationControllerDelegate
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
{
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
ASAuthorizationAppleIDCredential*credential = authorization.credential;
NSString*state = credential.state;
NSString*userID = credential.user;
NSPersonNameComponents*fullName = credential.fullName;
NSString*email = credential.email;
NSString*authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding]; // refresh token
NSString*identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding]; // access token
ASUserDetectionStatus realUserStatus= credential.realUserStatus;
NSLog(@"state: %@", state);
NSLog(@"userID: %@", userID);
NSLog(@"fullName: %@", fullName);
NSLog(@"email: %@", email);
NSLog(@"authorizationCode: %@", authorizationCode);
NSLog(@"identityToken: %@", identityToken);
NSLog(@"realUserStatus: %@", @(realUserStatus));
}
}
//当我们授权成功后,我们可以在 authorizationController:didCompleteWithAuthorization: 这个代理方法中获取到ASAuthorizationAppleIDCredential ,通过这个可以拿到用户的 userID、email、fullName、authorizationCode、identityToken 以及 realUserStatus 等信息。
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0))
{
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;
}
NSLog(@"%@", errorMsg);
}
这些信息具体含义和用途:
∙ User ID: Unique, stable, team-scoped user ID,苹果用户唯一标识符,该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
∙ Verification data: Identity token, code,验证数据,用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性,详见 Sign In with Apple REST API。如果验证成功,可以根据userIdentifier 判断账号是否已存在,若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录态给 App。
∙ Account information: Name, verified email,苹果用户信息,包括全名、邮箱等。
∙ Real user indicator: High confidence indicator that
likely real user,用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal。
失败情况会走 authorizationController:didCompleteWithError: 这个方法,具体看代码吧。
[if !supportLists]3.4. [endif]处理用户授权变更
通过上面的步骤一个完整的授权,已经完成。BUT,我们还需要处理一些 Case。
∙ 用户终止 App 中使用 Sign in with Apple 功能
∙ 用户在设置里注销了AppleId
这些情况下,App 需要获取到这些状态,然后做退出登录操作,或者重新登录。我们需要在 App 启动的时候,通过 getCredentialState:completion: 来获取当前用户的授权状态。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (@available(iOS 13.0, *)) {
NSString*userIdentifier = 钥匙串中取出的 userIdentifier;
if (userIdentifier) {
ASAuthorizationAppleIDProvider*appleIDProvider = [ASAuthorizationAppleIDProvider new];
[appleIDProvider getCredentialStateForUserID:userIdentifier
completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState,
NSError* _Nullable error)
{
switch (credentialState) {
case ASAuthorizationAppleIDProviderCredentialAuthorized:
// The Apple ID credential is valid
break;
case ASAuthorizationAppleIDProviderCredentialRevoked:
// Apple ID Credential revoked, handle unlink
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
// Credential not found, show login UI
break;
}
}];
}
}
return YES;
}
ASAuthorizationAppleIDProviderCredentialState 解析如下:∙ASAuthorizationAppleIDProviderCredentialAuthorized授权状态有效;∙ASAuthorizationAppleIDProviderCredentialRevoked上次使用苹果账号登录的凭据已被移除,需解除绑定并重新引导用户使用苹果登录;∙ASAuthorizationAppleIDProviderCredentialNotFound未登录授权,直接弹出登录页面,引导用户登录。
另外,在 App 使用过程中,你还可以通过通知方法来监听 revoked 状态,可以添加 ASAuthorizationAppleIDProviderCredentialRevokedNotification 这个通知,收到这个通知的时候,我们可以:
∙Sign user out on this device
∙Guide to sign in again
具体怎么添加和处理,可以根据业务需求来决定。
- (void)observeAppleSignInState
{
if (@available(iOS 13.0, *)) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleSignInWithAppleStateChanged:)
name:ASAuthorizationAppleIDProviderCredentialRevokedNotification
object:nil];
}
}
- (void)handleSignInWithAppleStateChanged:(NSNotification *)notification
{
// Sign the user out, optionally guide them to sign in again
NSLog(@"%@", notification.userInfo);
}
[if !supportLists]3.5. [endif]One more thing
除此之外,苹果还把 iCloud KeyChain password 集成到了这套 API 里,我们在使用的时候,只需要在创建 request 的时候,多创建一个 ASAuthorizationPasswordRequest,这样如果KeyChain 里面也有登录信息的话,可以直接使用里面保存的用户名和密码进行登录。代码如下:
- (void)signInWithAppleAPI_AVAILABLE(ios(13.0))
{
ASAuthorizationAppleIDProvider*appleIDProvider = [ASAuthorizationAppleIDProvider new];
ASAuthorizationAppleIDRequest*authAppleIDRequest = [appleIDProvider createRequest];
ASAuthorizationPasswordRequest*passwordRequest = [[ASAuthorizationPasswordProvider new] createRequest];
NSMutableArray
if (authAppleIDRequest) {
[array addObject:authAppleIDRequest];
}
if (passwordRequest) {
[array addObject:passwordRequest];
}
NSArray
ASAuthorizationController*authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
authorizationController.delegate = self;
authorizationController.presentationContextProvider = self;
[authorizationController performRequests];
}
#pragma mark - ASAuthorizationControllerDelegate
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
{
if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
ASPasswordCredential*passwordCredential = authorization.credential;
NSString*userIdentifier = passwordCredential.user;
NSString*password = passwordCredential.password;
NSLog(@"userIdentifier: %@", userIdentifier);
NSLog(@"password: %@", password);
}
}
4.本地化:必要且重要的一点
按钮显示的文字都是英文的,而且我们不可以修改文字,总不能在国内也展示个英文只需要添加本地化支持就可以了,此方法适用于一切系统文案,比如我们创建的 UIBarButtonSystemItemSave ,添加简体中文支持后,他显示的文案就变成 保存
5.Sign In with Apple 和 Continue with Apple登录的区别
只是文案的不同而已,中文意思分别为:通过Apple登录、通过Apple继续
6.苹果第三方登录与己方后台服务账号相互认证问题
在第三方登录出现之前,大部分用户账户注册与登录流程都是双输的局面:
用户方面:为了安全性而设置的密码往往复杂又冗余,既容易忘记输入又很麻烦,同时各种难度的验证码和各种验证问题,不仅过滤机器人也能过滤真人,加上手机验证码等等繁琐步骤,让注册登录变得异常烦心。
服务商方面:加上各种验证步骤会明显降低用户体验,可是不加又会导致用户账户不安全和各种机器人账号泛滥,更不用说还要自己探索实现各种验证步骤,一不小心就会出现漏洞。很多时候最后这些繁琐的步骤也没有挡住日益智能的机器人,反而挡住了很多正常用户。
[if !supportLists]· [endif]为了解决上面提到的各种问题,开放授权(OAuth)在 2010 年应运而生。简单来讲它是一个开放标准,允许用户在不提供密码的情况下,授权接入了 A 网站第三方登录支持的第三方应用,访问自己在 A 网站上的特定数据。
[if !supportLists]· [endif]接着为了解决安全性问题,OAuth
2.0 诞生了,同时有人发现了这个标准很适合用来登录验证用户身份,于是基于此延伸的标准,专门解决第三方客户端标使用身份认证的问题的 OIDC(OpenID Connect) 诞生了。现在我们见到的大多数第三方登录都是基于 OIDC 或者利用 OAuth 2.0 修改实现。
第三方登录的原理
因为 QQ/微信/微博等平台(以下简称第一方平台)拥有完整的登录验证步骤,用户在上面已经充分地证明了「我是真的我」。所以,第三方网站与服务只要接入了这些平台的第三方登录 SDK(开发组件),用户点击第三方登录按钮时:
网站或服务会通过第三方登录 SDK 向第一方平台发送登录请求。
第一方平台接收到登录请求,确认用户设备上有没有已经登录的第一方平台账户,如果没有用户需要先手动登录第一方平台。
如果用户已经在设备上登录了第一方平台账户,那么第一方平台会返回唯一的不变的 ID(OpenID)。
第三方网站与服务将这个 ID 储存到自己的服务器上,以后就能通过这个 ID 确认用户了。