One-tap account security upgrades
Privacy & Security 入门 14m

一键提升账户安全性

One-tap account security upgrades

2020年6月24日

在 Apple 官方观看视频

一句话判断

iOS 14 让「升级到 Sign in with Apple」变成了一键操作——如果你的 app 之前用邮箱+密码注册,现在可以用 ASAuthorizationAppleIDProvider 的 credential 密集合让用户一键关联 Apple 账号,大幅降低安全风险和密码管理负担。

这场 Session 讲了什么

这场 Session 专注于如何在现有 app 中引导用户从传统认证方式(邮箱密码、社交登录)升级到 Sign in with Apple,以及如何利用 Apple 的安全特性提升账户安全。

核心内容包括:使用 ASAuthorizationAppleIDProvider 实现一键账户关联、利用 ASAuthorizationPasswordProvider 自动填充已保存的密码、如何检测 Apple ID 凭证状态变化(用户撤销授权时及时处理)、以及如何在服务端验证 Apple ID Token。

Session 还介绍了 iOS 14 的新功能:当用户在一个网站或 app 上使用 Sign in with Apple 时,系统会检查该用户的邮箱是否出现在已知的数据泄露中,如果发现风险会主动提醒用户更换密码。

值得深挖的点

Credential 密集与一键关联

iOS 14 新增的「密码密集」(Credential Density)概念是指:当系统检测到你的 app 同时支持 Sign in with Apple 和传统密码登录时,会在键盘上方的 QuickType bar 中同时展示两种选项。用户可以选择 Sign in with Apple 来关联已有账号,而不是重新注册一个新账号。这个「关联」流程是你需要在 app 中实现的:检测到同一邮箱的已有账号后,把 Apple ID 绑定到这个账号上。

服务端凭证状态监控

用户可能在系统设置中撤销了对你的 app 的 Sign in with Apple 授权。你的服务端需要定期调用 Apple 的 Token Revocation API 来检查 sub(用户唯一标识)的撤销状态。一旦发现撤销,应该让 app 回退到其他认证方式。

代码片段

import AuthenticationServices

// 一键关联:引导用户用 Sign in with Apple 关联已有账号
class AccountUpgradeManager: UIViewController {
    
    func upgradeToSignInWithApple() {
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.performRequests()
    }
}

extension AccountUpgradeManager: ASAuthorizationControllerDelegate {
    
    func authorizationController(controller: ASAuthorizationController, 
                                  didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
        
        let userIdentifier = credential.user
        let email = credential.email
        let identityToken = credential.identityToken
        
        // 将 Apple ID 关联到已有账号
        linkAppleID(toExistingAccount: userIdentifier, email: email, token: identityToken)
    }
    
    func authorizationController(controller: ASAuthorizationController, 
                                  didCompleteWithError error: Error) {
        print("Sign in with Apple 失败: \(error)")
    }
}
// 检查 Apple ID 授权状态
func checkAppleIDStatus(userIdentifier: String) {
    let provider = ASAuthorizationAppleIDProvider()
    provider.getCredentialState(forUserID: userIdentifier) { state, error in
        switch state {
        case .authorized:
            // 授权有效,正常使用
            break
        case .revoked:
            // 用户已撤销授权,需要重新登录或使用其他认证方式
            self.handleRevokedAuthorization()
        case .notFound:
            // 用户 ID 不存在,可能已注销
            self.handleNotFound()
        default:
            break
        }
    }
}

// 在 app 启动时检查
func applicationDidBecomeActive(_ application: UIApplication) {
    if let appleUserID = UserDefaults.standard.string(forKey: "appleUserID") {
        checkAppleIDStatus(userIdentifier: appleUserID)
    }
}

最佳实践

  1. 在用户已登录后提示升级。不要强迫用户立即切换到 Sign in with Apple。在设置页面或账户安全页面提供一个「使用 Apple 登录以提升安全性」的入口,让用户主动选择。
  2. 关联流程要处理邮箱不一致的情况。用户可能用 A 邮箱注册了账号,但 Sign in with Apple 的隐藏邮箱是 B 邮箱。关联时要允许用户手动确认是否是同一个账号。
  3. 服务端定期检查撤销状态。苹果会在用户撤销授权后的 24 小时内更新状态。建议每天至少检查一次活跃用户的 Apple ID 状态。

还有什么值得关注

  • Sign in with Apple 的 hidden email 功能可以让用户不暴露真实邮箱,你的 app 通过苹果提供的代理邮箱联系用户。
  • iOS 14 的密码安全监控(Security Recommendations)会检测已保存密码是否出现在数据泄露中,并在设置 app 中提醒用户。
  • 对于 Web 端,Sign in with Apple 现在支持通过 JavaScript API 直接调用,不需要加载 Apple 的 JS SDK。
WWDC 2020