使用通行密钥登录

本指南将继续介绍如何实现使用通行密钥进行身份验证。 在用户可以使用通行密钥登录之前,您还必须完成创建通行密钥中的说明。

如需使用通行密钥进行身份验证,您必须先从应用服务器检索检索公钥所需的选项,然后调用 Credential Manager API 来检索公钥。然后,妥善处理登录响应。

概览

本指南重点介绍在客户端应用中需要进行的更改,以便用户使用通行密钥登录,并简要概述应用服务器端实现。如需详细了解服务器端集成,请参阅服务器端通行密钥身份验证

如需检索与用户账号关联的所有通行密钥和密码选项,请完成以下步骤:

  1. 从服务器获取凭据请求选项:从您的应用向身份验证服务器发出请求,以启动通行密钥登录流程。从服务器发送获取公钥凭据所需的选项,以及一个唯一的质询。
  2. 创建获取公钥凭据所需的对象:将服务器发送的选项封装在 GetPublicKeyCredentialOption 对象中
  3. 可选)准备 getCredential:在 Android 14 及更高版本中,您可以在调用 getCredential() 之前使用 prepareGetCredential() 方法显示账号选择器,从而缩短延迟时间。
  4. 启动登录流程:调用 getCredential() 方法以登录用户
  5. 处理响应:处理每种可能的凭据响应。
  6. 处理异常:确保您能妥善处理异常。

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)
    }
}

后续步骤