透過憑證管理工具讓使用者登入

憑證管理工具是全新的 Jetpack API,可在單一 API 中支援多種登入方式,像是使用者名稱/密碼、密碼金鑰和聯合登入解決方案 (例如使用 Google 帳戶登入),因此可為開發人員簡化整合作業。

此外,不論使用者選擇的驗證方法為何,憑證管理工具都能讓使用者透過單一登入介面登入,因此使用者可以更輕鬆簡便地登入應用程式。

本頁面說明密碼金鑰的概念,以及如何使用 Credential Manager API,針對密碼金鑰等驗證解決方案實作用戶端支援功能。您也可以查看另外的常見問題頁面,瞭解更詳細具體的問題與解答。

您的意見回饋對改善 Credential Manager API 至關重要。如有任何問題或改善 API 的想法,都可以使用下方連結告訴我們:

提供意見

關於密碼金鑰

密碼金鑰比密碼更加安全方便。密碼金鑰可讓使用者透過生物特徵辨識感應器 (例如指紋或臉部辨識)、PIN 碼或解鎖圖案登入應用程式和網站。這麼做可以提供流暢的登入體驗,讓使用者不必記憶使用者名稱或密碼。

密碼金鑰採用 WebAuthn (網路驗證),這是由 FIDO 聯盟和全球資訊網協會 (World Wide Web Consortium,W3C) 共同制定的標準。WebAuthn 使用公開金鑰密碼編譯機制來驗證使用者。使用者登入的網站或應用程式可以查看及儲存公開金鑰,但無法查看及儲存私密金鑰。私密金鑰會妥善保密,確保安全無虞。由於金鑰是獨一無二的,且與網站或應用程式綁定,因此密碼金鑰不會遭到網路釣魚攻擊,可提升安全性。

憑證管理工具可讓使用者建立密碼金鑰,並且儲存在 Google 密碼管理工具中。

必要條件

如要使用憑證管理工具,請完成本節中的步驟。

使用最新的平台版本

Android 4.4 (API 級別 19) 以上版本都支援憑證管理工具。

為應用程式新增依附元件

請將下列依附元件新增至應用程式模組的建構指令碼:

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.0.0-alpha02"

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.0.0-alpha02"
}

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.0.0-alpha02")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.0.0-alpha02")
}

保留 ProGuard 檔案中的類別

在模組的 proguard-rules.pro 檔案中,新增下列指令:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}

進一步瞭解如何縮減、模糊化及最佳化應用程式

新增對 Digital Asset Links 的支援

如要讓 Android 應用程式支援密碼金鑰,請將應用程式與應用程式所擁有的網站建立關聯。完成下列步驟即可宣告此關聯:

  1. 建立 Digital Asset Links JSON 檔案。舉例來說,如要宣告網站 https://signin.example.com 和含有套件名稱 com.example 的 Android 應用程式可以共用登入憑證,請建立名為 assetlinks.json 的檔案,其中含有以下內容:

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    relation 欄位是一或多個字串陣列,用來描述要宣告的關係。如要宣告應用程式和網站共用登入憑證,請指定 delegate_permission/common.get_login_creds 字串。

    target 欄位是一種物件,用於指定宣告所適用的資產。下列欄位可用來識別網站:

    namespace web
    site

    網站網址,格式為 https://domain[:optional_port],例如 https://www.example.com

    domain 必須完整,且使用 HTTPS 的通訊埠 443 時必須省略 optional_port

    site 指定目標只能是根網域:您無法限制應用程式與特定子目錄建立關聯。請勿在網址中加入路徑,例如結尾的斜線。

    系統不會將子網域視為相符項目:也就是說,如果將 domain 指定為 www.example.com,網域 www.counter.example.com 就不會與您的應用程式建立關聯。

    下列欄位可用來識別 Android 應用程式:

    namespace android_app
    package_name 應用程式資訊清單中宣告的套件名稱。例如:com.example.android
    sha256_cert_fingerprints 應用程式簽署憑證的 SHA256 指紋。
  2. 請在登入網域的以下位置代管 Digital Assets Link JSON 檔案:

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    舉例來說,如果您的登入網域為 signin.example.com,請在 https://signin.example.com/.well-known/assetlinks.json 上代管 JSON 檔案。

    Digital Assets Link 檔案的 MIME 類型必須為 JSON。確認伺服器會在回應中傳送 Content-Type: application/json 標頭。

  3. 確保主機允許 Google 擷取您的 Digital Asset Link 檔案。如果您有 robots.txt 檔案,必須允許 Googlebot 代理程式擷取 /.well-known/assetlinks.json。大多數網站會直接允許所有自動化代理程式擷取 /.well-known/ 路徑中的檔案,讓其他服務存取這些檔案內的中繼資料:

    User-agent: *
    Allow: /.well-known/
    

設定憑證管理工具

如要設定及初始化 CredentialManager 物件,請新增類似以下的邏輯:

Kotlin

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
val credentialManager = CredentialManager.create(context)

Java

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
CredentialManager credentialManager = CredentialManager.create(context)

表示憑證欄位

在 Android 14 以上版本中,isCredential 屬性可用來表示憑證欄位,例如使用者名稱或密碼欄位。此屬性表示這個檢視區塊是一個憑證欄位,可讓憑證管理工具和第三方憑證提供者使用,同時協助自動填入服務提供更符合需求的自動填入建議。應用程式使用憑證管理工具 API 時,系統會顯示憑證管理工具底部功能表,並列出可用憑證,因此您就不必再採取後續行動來顯示使用者名稱/密碼的自動填入對話方塊。

如要使用 isCredential 屬性,請將該屬性新增至相關的檢視區塊:

<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:isCredential="true"
...
 />

讓使用者登入

如要擷取與使用者帳戶相關聯的所有密碼金鑰和密碼選項,請完成下列步驟:

  1. 初始化密碼和密碼金鑰驗證選項:

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. 使用從上一個步驟擷取的選項,建構登入要求:

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. 啟動登入流程:

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    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")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onSuccess(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onFailure(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // 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");
        }
    }

以下範例說明在取得密碼金鑰時,如何設定 JSON 要求的格式:

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

以下範例說明在取得公開金鑰憑證後,JSON 回應可能會如何呈現:

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

註冊流程

您可以使用密碼金鑰密碼,為使用者註冊驗證程序。

建立密碼金鑰

如要讓使用者能選擇註冊密碼金鑰,並用於重新驗證,請使用 CreatePublicKeyCredentialRequest 物件註冊使用者憑證:

首次呼叫 Credential Manager API 時,請將 preferImmediatelyAvailableCredentials 設為 truepreferImmediatelyAvailableCredentials 方法會定義您完成這項要求時,是否偏好只使用即時可用的憑證,而非使用混合式憑證。若是 true,則在沒有可用的憑證時,Credential Manager API 不會顯示遠端項目。根據預設,此值為 false

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialUnknownException -> ...
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof CreateCredentialCustomException) {
                    // You have encountered an error from a 3rd-party SDK. If
                    // you make the API call with a request object that's a
                    // subclass of
                    // CreateCustomCredentialRequest using a 3rd-party SDK,
                    // then you should check for any custom exception type
                    // constants within that SDK to match with e.type.
                    // Otherwise, drop or log the exception.
                } else {
                  Log.w(TAG, "Unexpected exception type "
                          + e.getClass().getName());
                }
            }
        }
    );
}

設定 JSON 要求格式

建立密碼金鑰後,您必須將密碼金鑰與使用者帳戶建立關聯,並將密碼金鑰的公開金鑰儲存在伺服器上。下列程式碼片段示範如何在建立密碼金鑰時,設定 JSON 要求的格式。

這篇網誌文章說明如何為應用程式順暢地進行驗證,包括如何在建立密碼金鑰及使用密碼金鑰進行驗證時,設定 JSON 要求的格式。文章中也會說明為何密碼不是有效的驗證解決方案、如何利用現有的生物特徵辨識憑證、如何將應用程式與您擁有的網站建立關聯、如何建立密碼金鑰,以及如何使用密碼金鑰進行驗證。

{
  "challenge": "nhkQXfE59Jb97VyyNJkvDiXucMEvltduvcrDmGrODHY",
  "rp": {
    "name": "CredMan App Test",
    "id": "credential-manager-app-test.glitch.me"
  },
  "user": {
    "id": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

處理 JSON 回應

下列程式碼片段顯示建立公開金鑰憑證的 JSON 回應範例。進一步瞭解如何處理傳回的公開金鑰憑證

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

儲存使用者的密碼

如果使用者在應用程式中提供驗證流程的使用者名稱和密碼,您可以註冊使用者憑證。這個憑證可以用來驗證使用者。方法是建立 CreatePasswordRequest 物件:

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

支援憑證復原程序

如果使用者無法再存取已儲存憑證的裝置,可能需要透過安全的線上備份復原憑證。如要進一步瞭解如何支援這套憑證復原程序,請參閱《Google 密碼管理工具中的密碼金鑰安全性》(Security of Passkeys in the Google Password Manager) 網誌文章中的「復原存取權或新增裝置」(Recovering access or adding new devices) 一節。

支援使用密碼金鑰端點已知網址的密碼管理工具

為了與密碼和憑證管理工具完美整合,並提升日後的相容性,建議您新增密碼金鑰端點已知網址的支援功能。這是開放式的通訊協定,供聯盟中的各方正式宣傳自家對密碼金鑰的支援,並提供註冊及管理密碼金鑰的直接連結。

  1. 如果您是 https://example.com 的依賴方,且擁有網站和 Android 和 iOS 應用程式,則已知網址會是 https://example.com/.well-known/passkey-endpoints。

  2. 使用者查詢該網址時,回應應採用下列結構定義:

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. 如要直接在應用程式中 (而非在網頁上) 開啟這個連結,請使用 Android 應用程式連結

  4. 詳情請參閱 GitHub 的密碼金鑰端點已知網址說明文件。

排解常見錯誤

下表列出了幾個常見的錯誤代碼和說明,並提供造成這些錯誤的部分原因:

錯誤代碼和說明 原因
On Begin Sign In Failure: 16:由於取消的登入提示過多,導致呼叫端暫時遭到封鎖。

如果您在開發應用程式期間遇到 24 小時的等待期,只要清除 Google Play 服務的應用程式儲存空間即可重置等待期。

或者,如要在測試裝置或模擬器上啟用等待期,請前往「撥號」應用程式並輸入以下代碼:*#*#66382723#*#*。「撥號」應用程式會清除所有輸入內容,並且可能關閉應用程式,不過系統不會顯示確認訊息。

On Begin Sign In Failure: 8:未知的內部錯誤。
  1. 裝置尚未使用 Google 帳戶進行妥善設定。
  2. 密碼金鑰 JSON 建立方式有誤。
CreatePublicKeyCredentialDomException:無法驗證傳入要求 應用程式的套件 ID 並未在您的伺服器上註冊。請在伺服器端整合中進行確認。
CreateCredentialUnknownException:儲存密碼時,輕觸一下即可找到密碼失敗回應 16:Android 自動填入功能可能會提示使用者,因此略過密碼儲存程序 這是 Android 13 以下版本才有的錯誤,且只在 Google 是自動填入供應商時才會發生。遇到此情況時,自動填入功能會向使用者顯示儲存提示,並將密碼儲存至 Google 密碼管理工具。請注意,透過 Google 自動填入功能儲存的憑證會與憑證管理工具 API 雙向共用。因此,您可以放心忽略這個錯誤。

其他資源

如要進一步瞭解 Credential Manager API 和密碼金鑰,請參閱下列資源: