什么是Passkeys?
Passkeys是苹果提供的一种账户密码验证方式的替代品,它创建账户和登录账户无需用户输入密码。它只需要验证用户的TouchId或者FaceId。
预置条件
- 用户设备iOS 16.0+ 、iPadOS 16.0+ 、Mac Catalyst 16.0+
- 用户设备启用iCloud钥匙串。
- 开发者Xcode 14.0+
- 开发者在服务器根目录放置文件apple-app-site-association。放置后,用户可以通过https://
/.well-known/apple-app-site-association 直接访问下载。比如域名是lcslearn.top,通过访问https://lcslearn.top/.well-known/apple-app-site-association可以直接下载。资料参考https://developer.apple.com/documentation/xcode/supporting-associated-domains#Add-the-associated-domain-file-to-your-website
原理
客户端生成公私钥对,把公钥共享给服务器端。服务器端用公钥对用户的数据进行验证,以进行创建账户和登录操作。
在创建和登录的过程中会用到faceid和touchid,这两项是生物识别的技术,可以识别用户是否本人。然后取出存储在本地设备的信息。这个是通用的。如果是账户密码,这个本地信息就是密码。如果是passkeys,这个本地信息就是私钥签名后的数据。然后把这个本地信息发送到服务器进行鉴定。
服务器端存储的是公钥,这个公钥对黑客价值较低,黑客拿到也没用。所以passkeys比账户密码安全。客户端设备存储私钥,用户不用记密码了,也不用保存私钥,私钥对用户不可见,每次需要使用私钥的时候,需要使用faceid或者touchid验证。用户只需要验证faceid和touchid,所以更简单,体验更好。
每一对密钥都会绑定到一个域名,所以不存在钓鱼攻击了。
一句话,Passkeys是非常安全、易用、便捷的账户管理方式,非常推荐使用!
时序图
代码
//创建账户
func signUpWith(userName: String, anchor: ASPresentationAnchor) {
self.authenticationAnchor = anchor
let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
// Fetch the challenge from the server. The challenge needs to be unique for each request.
// The userID is the identifier for the user's account.
let challenge = "123456789000".data(using: .utf8)!
let userID = Data(UUID().uuidString.utf8)
let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge,
name: userName, userID: userID)
// Use only ASAuthorizationPlatformPublicKeyCredentialRegistrationRequests or
// ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequests here.
let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] )
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()
isPerformingModalReqest = true
}
//登录
func signInWith(anchor: ASPresentationAnchor, preferImmediatelyAvailableCredentials: Bool) {
self.authenticationAnchor = anchor
let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
// Fetch the challenge from the server. The challenge needs to be unique for each request.
let challenge = "1234567890".data(using: .utf8)
let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challenge!)
// Also allow the user to use a saved password, if they have one.
let passwordCredentialProvider = ASAuthorizationPasswordProvider()
let passwordRequest = passwordCredentialProvider.createRequest()
// Pass in any mix of supported sign-in request types.
let authController = ASAuthorizationController(authorizationRequests: [ assertionRequest, passwordRequest ] )
authController.delegate = self
authController.presentationContextProvider = self
if preferImmediatelyAvailableCredentials {
// If credentials are available, presents a modal sign-in sheet.
// If there are no locally saved credentials, no UI appears and
// the system passes ASAuthorizationError.Code.canceled to call
// `AccountManager.authorizationController(controller:didCompleteWithError:)`.
authController.performRequests(options: .preferImmediatelyAvailableCredentials)
} else {
// If credentials are available, presents a modal sign-in sheet.
// If there are no locally saved credentials, the system presents a QR code to allow signing in with a
// passkey from a nearby device.
authController.performRequests()
}
isPerformingModalReqest = true
}
//处理回调
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
let logger = Logger()
switch authorization.credential {
case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
logger.log("A new passkey was registered: \(credentialRegistration)")
// Verify the attestationObject and clientDataJSON with your service.
// The attestationObject contains the user's new public key to store and use for subsequent sign-ins.
// let attestationObject = credentialRegistration.rawAttestationObject
// let clientDataJSON = credentialRegistration.rawClientDataJSON
// After the server verifies the registration and creates the user account, sign in the user with the new account.
didFinishSignIn()
case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
logger.log("A passkey was used to sign in: \(credentialAssertion)")
// Verify the below signature and clientDataJSON with your service for the given userID.
// let signature = credentialAssertion.signature
// let clientDataJSON = credentialAssertion.rawClientDataJSON
// let userID = credentialAssertion.userID
// After the server verifies the assertion, sign in the user.
didFinishSignIn()
case let passwordCredential as ASPasswordCredential:
logger.log("A password was provided: \(passwordCredential)")
// Verify the userName and password with your service.
// let userName = passwordCredential.user
// let password = passwordCredential.password
// After the server verifies the userName and password, sign in the user.
didFinishSignIn()
default:
fatalError("Received unknown authorization type.")
}
isPerformingModalReqest = false
}