简介
在WWDC 2019,苹果推出了Sign In with Apple
这一iOS 13的新特性,用户可以直接利用苹果ID登陆应用,免去了输入邮箱、密码,验证登陆邮箱等繁琐的步骤。同时Sign In with Apple
提供了跨平台特性和安全性的提高。
另一方面它也提出了新的审核要求,在新的要求中提到但凡包含了第三方登录的应用,则同样需要适配Sign In with Apple
,否则会有审核风险:
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
需要用户开启了两步认证,如果没有开启则会在第一次使用时提示开启,不开启将无法使用。
如何接入 Sign In with Apple
1. 项目配置
添加Sign in with Apple
的capability
:
并在项目中加入AuthenticationServices.framework
即可。当需要使用时,需要在文件中添加<AuthenticationServices/AuthenticationServices.h>
引用。
2. 添加内置的登录按钮
登录按钮可以使用苹果推荐的按钮ASAuthorizationAppleIDButton
,具体的设计样式可以参看这里,大致的样式如下:
注意:
- 内置的登录按钮需要放在显眼的位置,没有强调一定要放在首位,但是提出不能让用户滚动后才看到这一个按钮。
- 内置的登录按钮的大小和圆角是可以自行调整的,但是大小有最大和最小的限制。
- 没有强调一定要使用内置的登陆按钮,但是在设计指南上指出最好使用样式相近的设计。
处理登录事件
3.1 创建请求
3.1.1 新用户登录
在新用户点击内置的登录按钮,期望使用苹果ID进行注册和登录时,我们需要使用ASAuthorizationAppleIDProvider
来创建一个ASAuthorizationAppleIDRequest
请求,在这个请求中,我们可以配置一个ASAuthorizationScope
数组,来规定需要用户提供什么样的信息,目前ASAuthorizationScope
仅包含两个:
- ASAuthorizationScopeEmail: 需要用户提供电子邮件地址
- ASAuthorizationScopeFullName: 需要用户提供姓名
需要注意的是,需要提供电子邮件时,用户是可以选择隐藏真实的邮件地址,这样获取到的邮件地址是这样的:
r45N934br1@privaterelay.appleid.com
该邮箱收到的邮件会转发到用户真实的邮箱上。另外需要提供姓名时,用户是可以对姓名进行修改的,而且是任意修改。
在用户填写完姓名与选择是否隐藏邮箱后,即可以输入苹果ID的密码,并自动完成注册过程,如果无网络会无法继续,停留在在这个页面。
代码实例:
// 创建请求
ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
ASAuthorizationAppleIDRequest *request = appleIDProvider.createRequest;
[request setRequestedScopes:@[ASAuthorizationScopeFullName,ASAuthorizationScopeEmail]];
3.1.2 旧用户登录
如果我们的APP用户之前已经登陆过,并且在keyChain
上保存了用户名和密码时,可以同时使用ASAuthorizationAppleIDProvider
和ASAuthorizationPasswordProvider
来创建请求
这种方式是针对用户已经使用账号密码登录过该app,并且已经将他们保存到keyChain
中的情况,由于对我现在项目目前的状态来说意义不大,所以不在本文进行讨论。
3.2 发起请求
发起请求需要用到ASAuthorizationController
,它可以同时发起多个Provider
的请求。
代码实例:
// 发起请求
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
在发起请求后就会出现Sign In with Apple
的UI,在用户完成了登录等操作后会返回请求结果由我们app进行处理。
3.3 处理请求结果
ASAuthorizationController
在其中提供了ASAuthorizationControllerDelegate
代理,用于请求结果的回调,代理提供了两个代理方法:
成功回调:
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization
失败回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error
在成功的回调中,我们能够获取一个ASAuthorization
对象,这个对象有provider
与credential
这两个属性,其中provider
属性能够让我们知道是哪一类的Provider
发起的请求,而credential
则是苹果账号登录结果的一个认证。
在“新用户登录”的情况下,返回的credential
为ASAuthorizationAppleIDCredential
,而“旧用户登录”的情况下,则返回ASPasswordCredential
。
我们侧重于ASAuthorizationAppleIDCredential
属性的解析:
- User ID: 苹果用户唯一标识符,它在同一个开发者账号下的所有 App 下是一样的,我们可以用它来与后台的账号体系绑定起来(类似于微信的
OpenID
)。- Verification Data: 包括
identityToken
,authorizationCode
。用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性。- Account Information: Name, verified email,苹果用户信息,包括全名、邮箱等。
- Real User Indicator: 用于判断当前登录的苹果账号是否是一个真实用户,取值有:
unsupported
、unknown
、likelyReal
。
代码实例:
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
//Sign with Apple 成功
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
//此时为使用Sign With Apple 方式登录
ASAuthorizationAppleIDCredential *credential = authorization.credential;
NSString *userID = credential.user;
NSString *fullname = credential.fullName;
NSData *token = credential.identityToken
//继续进行客户端后台登录验证
}
}
3.4 与服务器验证数据
时序图
整体流程与普通的第三方登录十分相似,同样需要获取用户ID与token,交给后台验证本次登录的有效性。
验证流程相关
- 步骤1与2在客户端内完成,客户端选择使用
ASAuthorizationAppleIDProvider
来完成登录,如果登录成功,苹果将会返回如下数据:
- User ID: 苹果用户唯一标识符,它在同一个开发者账号下的所有 App 下是一样的,我们可以用它来与后台的账号体系绑定起来(类似于微信的
OpenID
)。- Verification Data: 包括
identityToken
,authorizationCode
。用于传给开发者后台服务器,然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性。- Account Information: 苹果用户信息,包括全名、邮箱等,登录时用户可以选择隐藏真实的邮件地址和随意修改姓名。
- Real User Indicator: 用于判断当前登录的苹果账号是否是一个真实用户,取值有:
unsupported
、unknown
、likelyReal
。
- 步骤3与4中,客户端会把
identityToken
,authorizationCode
,userID
这三个参数传给服务器,用于验证本次登录的有效性。
其中identityToken
是一个经过签名的JSON Web Token(JWT),它包含了:
它分为了三个部分:
- header: 包括了key id 与加密算法
- payload:
- iss: 签发机构,苹果
- aud: 接收者,目标app
- exp: 过期时间
- iat: 签发时间
- sub: 用户id
- c_hash: 一个哈希数列,作用未知
- auth_time: 签名时间
- signature: 用于验证JWT的签名
服务端在获取客户端发出的identityToken
后,需要进行如下步骤:
- 需要逆向构造过程,decode出JWT的三个部分
- 从https://appleid.apple.com/auth/keys中获取公钥,并将公钥转换为
pem
对JWT进行验证 - 如
identityToken
通过验证,则可以根据其payload中的内容进行验证等操作
token验证原理:
因为
idnetityToken
使用非对称加密 RSASSA【RSA签名算法】 和 ECDSA【椭圆曲线数据签名算法】,当验证签名的时候,利用公钥来解密Singature,当解密内容与base64UrlEncode(header) + "." + base64UrlEncode(payload)
的内容完全一样的时候,表示验证通过。
防止中间人攻击原理:
该token是苹果利用私钥生成的一段JWT,并给出公钥我们对token进行验证,由于中间人并没有苹果的私钥,所以它生成出来的token是没有办法利用苹果给出的公钥进行验证的,确保的token的安全性。
4. 处理 Apple ID 登录状态变化
用户利用Apple ID
进行登录,所以应该对ID退出登录等情况进行处理。另外用户在利用Apple ID
登录后,可以在设置页面删除曾经登录过的应用,类似于解除绑定的操作,这时应用也需要做对应处理。
苹果提供了一个快速的API来让我们查询用户的Apple ID
状态:
- [ASAuthorizationAppleIDProvider getCredentialStateForUserID:completion:]
这个接口利用在登录时获取的userID
,能够快速返回帐号状态:
- authorized: 已认证
- notFound: 用户可能尚未将帐号与Apple ID绑定
- revoked: 账号已经注销
这个方法应该在启动时与应用返回前台时调用,确保账号状态能够及时更新。
总结
- 作为一种类似于三方登录的登录方式,后台方面需要关注的是登录状态的验证、账号和apple userID绑定的逻辑。
- 苹果账号切换、注销等状态会在客户端内处理,并在状态发生变化时登出帐号。
另外苹果在与后台验证一块的文档过于语焉不详,web端与app端的验证流程也有十分大的区别,让人头大。
更多内容可以关注我的博客