使用密碼金鑰登入

本指南將繼續說明如何實作密碼金鑰驗證功能。您也必須先完成「建立密碼金鑰」一文中的操作說明,使用者才能使用密碼金鑰登入。

如要使用密碼金鑰進行驗證,您必須先從應用程式伺服器擷取公開金鑰所需的選項,然後呼叫 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)
    }
}

後續步驟