本指南将继续介绍如何实现使用通行密钥进行身份验证。 在用户可以使用通行密钥登录之前,您还必须完成创建通行密钥中的说明。
如需使用通行密钥进行身份验证,您必须先从应用服务器检索检索公钥所需的选项,然后调用 Credential Manager API 来检索公钥。然后,妥善处理登录响应。
概览
本指南重点介绍在客户端应用中需要进行的更改,以便用户使用通行密钥登录,并简要概述应用服务器端实现。如需详细了解服务器端集成,请参阅服务器端通行密钥身份验证。
如需检索与用户账号关联的所有通行密钥和密码选项,请完成以下步骤:
- 从服务器获取凭据请求选项:从您的应用向身份验证服务器发出请求,以启动通行密钥登录流程。从服务器发送获取公钥凭据所需的选项,以及一个唯一的质询。
- 创建获取公钥凭据所需的对象:将服务器发送的选项封装在
GetPublicKeyCredentialOption对象中 - (可选)准备 getCredential:在 Android 14 及更高版本中,您可以在调用
getCredential()之前使用prepareGetCredential()方法显示账号选择器,从而缩短延迟时间。 - 启动登录流程:调用
getCredential()方法以登录用户 - 处理响应:处理每种可能的凭据响应。
- 处理异常:确保您能妥善处理异常。
1. 从服务器获取凭据请求选项
向服务器请求获取公钥凭据所需的选项,以及每次登录尝试独有的 challenge。如需详细了解服务器端实现,请参阅创建质询和创建凭据请求选项。
选项类似于以下内容:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
如需详细了解这些字段,请参阅有关使用通行密钥登录的博文。
2. 创建获取公钥凭据所需的对象
在应用中,使用这些选项创建 GetPublicKeyCredentialOption 对象。
在以下示例中,requestJson 表示服务器发送的选项。
// Get password logins from the credential provider on the user's device.
val getPasswordOption = GetPasswordOption()
// Get passkeys from the credential provider on the user's device.
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
requestJson = requestJson
)
然后,将 GetPublicKeyCredentialOption 封装在 GetCredentialRequest 对象中。
val credentialRequest = GetCredentialRequest(
// Include all the sign-in options that your app supports.
listOf(getPasswordOption, getPublicKeyCredentialOption),
// Defines whether you prefer to use only immediately available
// credentials or hybrid credentials.
preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
)
3. 可选:缩短登录延迟时间
在 Android 14 或更高版本中,您可以在调用 getCredential() 之前使用 prepareGetCredential() 方法缩短显示账号选择器时的延迟时间。
prepareGetCredential() 方法会返回一个已缓存的 PrepareGetCredentialResponse 对象。这样,下一步中的 getCredential() 方法就可以使用缓存的数据调出账号选择器。
coroutineScope {
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
// Include all the sign-in options that your app supports
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
}
4. 启动登录流程
调用 getCredential() 方法以向用户显示账号选择器。请参考以下代码段,了解如何启动登录流程:
coroutineScope {
try {
result = credentialManager.getCredential(
// Use an activity-based context to avoid undefined system UI
// launching behavior.
context = activityContext,
request = credentialRequest
)
handleSignIn(result)
} catch (e: GetCredentialException) {
// Handle failure
}
}
5. 处理响应
处理响应,该响应可以包含各种类型的凭据对象。
fun handleSignIn(result: GetCredentialResponse) {
// Handle the successfully returned credential.
val credential = result.credential
when (credential) {
is PublicKeyCredential -> {
val responseJson = credential.authenticationResponseJson
// Share responseJson i.e. a GetCredentialResponse on your server to
// validate and authenticate
}
is PasswordCredential -> {
val username = credential.id
val password = credential.password
// Use id and password to send to your server to validate
// and authenticate
}
is CustomCredential -> {
// If you are also using any external sign-in libraries, parse them
// here with the utility functions provided.
if (credential.type == ExampleCustomCredential.TYPE) {
try {
val ExampleCustomCredential =
ExampleCustomCredential.createFrom(credential.data)
// Extract the required credentials and complete the authentication as per
// the federated sign in or any external sign in library flow
} catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
// Unlikely to happen. If it does, you likely need to update the dependency
// version of your external sign-in library.
Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
}
} else {
// Catch any unrecognized custom credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential")
}
}
}
从身份验证返回的 PublicKeyCredential 本质上是一个签名断言,结构如下:
{
"id": "<credential ID>",
"type": "public-key",
"rawId": "<raw credential ID>",
"response": {
"clientDataJSON": "<signed client data containing challenge>",
"authenticatorData": "<authenticator metadata>",
"signature": "<digital signature to be verified>",
"userHandle": "<user ID from credential registration>"
}
}
在服务器上,您必须验证凭据。如需了解详情,请参阅验证用户并让用户登录。
6. 处理异常
您应处理 GetCredentialException 的所有子类异常。如需了解如何处理每种异常,请参阅问题排查指南。
coroutineScope {
try {
result = credentialManager.getCredential(
context = activityContext,
request = credentialRequest
)
} catch (e: GetCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
}