这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
前言
最近公司的一个旧项目,非要做第三方登录,说是为了让用户在登录时体验更加友好。
我觉得吧,一个App了做了某个阶段,然后再考虑引入第三方登录,简直就是脑残。
目前App已经是通过手机号作为登录标识符,而且在后台的数据库中,用户的uid和手机号已经做了绑定,基本上可以认为手机号已经作为了主键使用。
现阶段又加入第三方登录,在用户首次第三方登录成功后,还是需要用户进行手机号的关联,我觉得有点脱裤子放屁的感觉。
大伙对这块怎么看,可以一起讨论,这个只是我比较激进的观点,因为害我要写功能,不能开心的在掘金上面摸鱼了。
当然说到加入第三方登录,那么根据Apple的规定,Apple登录也必须加上,否则无法上架。
所以才有了这篇文章,针对Apple登录的一点开发总结,虽然都是2年前的知识点,但我也算是旧瓶新酒,结合自己开发过程中App自身的业务情况,做了总结与思考,权当自己做笔记吧。
大伙注意看代码块中的注释喔!!!
Apple登录
开启该功能
我们自己的App要能够使用这个功能,需要在App中开启这个功能:
然后在developer开发网站中,Identifiers→对应App的BoundleID中也开启这个功能:
苹果官方Demo学习
我在编写这个功能的时候,非常仔细的研究了苹果官方给出的Demo代码。
有很多时候,当我们对某个新功能无从下手的时候,查看和学习官方代码是一个非常不错的例子。
Apple登录的逻辑处理,这一步非常的简单,就是起一个服务,设置好代理回调,请求走起!
@objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
复制代码
代理回调,这里大家注意看我代码里面的注释,比较重要喔:
extension LoginViewController: ASAuthorizationControllerDelegate {
/// - Tag: did_complete_authorization
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
/// 注意,第一次进行Apple登录,优先使用生物识别,指纹或者faceId如果此时生物识别没有通过,会提示你使用密码进行Apple登录,如果此时密码通过了,还是会走到这个case let appleIDCredential as ASAuthorizationAppleIDCredential中来,而一旦第一次生物识别过了,后生物识别没有通过,而依旧使用密码进行Apple登录,还是会走这里
case let appleIDCredential as ASAuthorizationAppleIDCredential:
/// 系统生成的user,相当于用户名
let userIdentifier = appleIDCredential.user
/// 将这个用户名保存到Keychain中
self.saveUserInKeychain(userIdentifier)
/// 这两个有且仅有在第一次使用Apple登录的时候才能获取到,第二次之后就拿不到这些数据了
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
/// 这个jwt字符串非常重要,需要传给App后台,App后台调用Apple后台,去验证账号的有效性
if let identityToken = appleIDCredential.identityToken {
let jwt = String(data: identityToken, encoding: .utf8)
print(jwt)
}
/// 给那种没有生物识别功能但是又使用Apple登录的设备,看来应该不是iPhone\iPad系列的产品,旧Mac\TV\Watch
case let passwordCredential as ASPasswordCredential:
// Sign in using an existing iCloud Keychain credential.
let username = passwordCredential.user
let password = passwordCredential.password
default:
break
}
}
}
复制代码
总体而言,这个Apple登录成功的回调中有重要的信息,在这里面我们需要做以下几个操作:
-
获取appleIDCredential.user
-
将appleIDCredential.user保存在Keychain中,为什么要保存,我们后面会说到
-
将appleIDCredential.identityToken转为jwt字符串,便于传递给App后台,然后App后台与Apple后台交互,做校验(虽然我们移动端不用关系后台与后台的交互问题,但是了解一点没坏处)
Apple登录的授权取消与App此时需要做的事情
这里我们又细分成为两种情况:
-
App通过Apple登录挂在后台,而此时去系统设置页面,删除了Apple登录在此App中的授权,此时App需要做对应的操作,我们可以通过监听通知的方式获知这个事情:
extension AppDelegate { func observeAppleSignInState() { NotificationCenter.default.addObserver(self, selector: #selector(handleSignInWithAppleStateChanged(notification:)), name: ASAuthorizationAppleIDProvider.credentialRevokedNotification, object: nil) } @objc func handleSignInWithAppleStateChanged(notification: NSNotification) { print(notification.userInfo) /// 删除在Keychain中保存当前Apple账号生成的appleIDCredential.user /// 做App登出等操作 } } 复制代码
-
App被杀死,而此时去系统设置页面,删除了Apple登录在此App中的授权,下一次点击进入App的时候,,我们可以通过系统给出的这个回调处理一些业务,同时需要注意,这里我们是通过从Keychain中获取之前保存的userIdentifier,才能获得其状态:
extension AppDelegate { func getCredentialState() { let appleIDProvider = ASAuthorizationAppleIDProvider() /// 注意这里的入参是KeychainItem.currentUserIdentifier,更详细的可以看官方Demo,这里注意是研究逻辑而不是代码实现 appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in switch credentialState { case .authorized: // The Apple ID credential is valid. break case .revoked, .notFound: // The Apple ID credential is either revoked or was not found, so show the sign-in UI. /// 删除在Keychain中保存当前Apple账号生成的appleIDCredential.user /// 如果App有自动登录功能,应该阻止,并删除相关对应账户的信息 break default: break } } } } 复制代码
-
把以上的两种情况的方法都添加到
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
中即可:func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { getCredentialState() observeAppleSignInState() return true } 复制代码
思考
大家需要注意这样一种业务情况:
App先使用Apple登录成功了,然后退出登录,再用QQ登录成功了,此时退出登录,然后我们取消Apple登录在此App中的授权,再次启动次App,根据上面写的逻辑,必然会走到处理Apple登录状态改变的逻辑中。
那么我们需要思考的问题也来了:
-
多种第三方登录业务混合的时候,如果对应Apple登录取消授权,是一个我们需要注意与考虑的地方。我在此次的App改造中,因为自身App的业务情况吃过亏,大家也需要注意,保存上一次成功第三方登录的方式有用处。
-
为何Apple登录的appleIDCredential.user要保存到Keychain中,保存到App沙盒不行吗?
-
为何在知道Apple登录授权取消时,我会删除在Keychain中保存的appleIDCredential.user?
参考文档
Implementing User Authentication with Sign in with Apple
总结
其实整体而言,Apple登录并不算特别复杂,它其实和你使用其他第三方登录,例如QQ、微信都是一个原理。
把这个流程图中的Apple后台换成QQ后台或者微信后台,其思路也一样的:
-
App先通过QQ SDK/微信 SDK获取登录标识符
-
App将登录标识符回传给App后台
-
App后台再将登录标识符给QQ后台/微信后台做校验
-
QQ后台/微信后台回调给App后台,App后台回传给App
这里我结合了业务情况,留下了思考和我对第三方登录的理解,或许在你也有集成Apple登录的时候会得到一点启发。