Credential Manager 是全新的 Jetpack API,可在單一 API 中支援多種登入方式,像是使用者名稱/密碼、密碼金鑰和聯合登入解決方案 (例如使用 Google 帳戶登入),因此可為開發人員簡化整合作業。
此外,不論使用者的驗證方法為何,Credential Manager 都能讓使用者透過單一登入介面登入,因此使用者可以更輕鬆簡便地登入應用程式。
本頁面說明密碼金鑰的概念,以及如何使用 Credential Manager API,針對密碼金鑰等驗證解決方案實作用戶端支援功能。您也可以查看另外的常見問題頁面,瞭解更詳細具體的問題與解答。
您的意見回饋對改善 Credential Manager API 至關重要。如有任何問題或改善 API 的想法,可以使用下方連結告訴我們:
關於密碼金鑰
密碼金鑰比密碼更加安全方便。密碼金鑰可讓使用者透過生物特徵辨識感應器 (例如指紋或臉部辨識)、PIN 碼或解鎖圖案登入應用程式和網站。這麼做可以提供流暢的登入體驗,讓使用者不必記憶使用者名稱或密碼。
密碼金鑰採用 WebAuthn (網路驗證),這是由 FIDO 聯盟和全球資訊網協會 (World Wide Web Consortium,W3C) 共同制定的標準。WebAuthn 使用公開金鑰密碼編譯機制來驗證使用者。使用者登入的網站或應用程式可以查看及儲存公開金鑰,但無法查看及儲存私密金鑰。私密金鑰會妥善保密,確保安全無虞。由於金鑰是獨一無二的,且與網站或應用程式綁定,因此密碼金鑰不會遭到網路釣魚攻擊,可提升安全性。
Credential Manager 可讓使用者建立密碼金鑰,並且儲存在 Google 密碼管理工具中。
如要瞭解如何使用 Credential Manager 實作順暢的密碼金鑰驗證流程,請參閱「使用密碼金鑰驗證使用者」一文。
必要條件
如要使用 Credential Manager,請完成本節中的步驟。
使用最新的平台版本
Android 4.4 (API 級別 19) 以上版本都支援 Credential Manager。
為應用程式新增依附元件
請將下列依附元件新增至應用程式模組的建構指令碼:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.5.0-alpha05") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.5.0-alpha05") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.5.0-alpha05" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.5.0-alpha05" }
保留 ProGuard 檔案中的類別
在模組的 proguard-rules.pro
檔案中,新增下列指令:
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
如要進一步瞭解如何縮減、模糊化及最佳化應用程式,請參閱這篇文章。
新增 Digital Asset Links 的支援
如要讓 Android 應用程式支援密碼金鑰,請將應用程式與應用程式所擁有的網站建立關聯。如要宣告此關聯,請完成下列步驟:
建立 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/handle_all_urls
和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 指紋。 請在登入網域的以下位置代管 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
標頭。確保主機允許 Google 擷取您的 Digital Asset Link 檔案。如果您有
robots.txt
檔案,必須允許 Googlebot 代理程式擷取/.well-known/assetlinks.json
。大多數網站會允許所有自動化代理程式擷取/.well-known/
路徑中的檔案,讓其他服務存取這些檔案內的中繼資料:User-agent: * Allow: /.well-known/
在資訊清單檔案
<application>
底下新增以下這行程式碼:<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
如果您是透過 Credential Manager 使用密碼登入,請按照此步驟在資訊清單中設定數位資產連結。如果您只使用密碼金鑰,則不需要執行這個步驟。
在 Android 應用程式中宣告關聯。新增物件,用於指定要載入的
assetlinks.json
檔案。您必須逸出在字串中使用的所有撇號和引號。例如:<string name="asset_statements" translatable="false"> [{ \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" }] </string>
> GET /.well-known/assetlinks.json HTTP/1.1 > User-Agent: curl/7.35.0 > Host: signin.example.com < HTTP/1.1 200 OK < Content-Type: application/json
設定 Credential Manager
如要設定及初始化 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
屬性可用來表示憑證欄位,例如使用者名稱或密碼欄位。此屬性表示這個檢視區塊是一個憑證欄位,可讓 Credential Manager 和第三方憑證提供者使用,同時協助自動填入服務提供更符合需求的自動填入建議。應用程式使用 Credential Manager API 時,系統會顯示 Credential Manager 底部功能表,並列出可用憑證,因此您就不必再採取後續行動來顯示使用者名稱/密碼的自動填入對話方塊。同樣地,應用程式會要求 Credential Manager API 儲存憑證,因此不需要顯示自動填入的密碼儲存對話方塊。
如要使用 isCredential
屬性,請將該屬性新增至相關的檢視區塊:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isCredential="true"
...
/>
讓使用者登入
如要擷取與使用者帳戶相關聯的所有密碼金鑰和密碼選項,請完成下列步驟:
初始化密碼和密碼金鑰驗證選項:
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);
使用從上一步擷取的選項建構登入要求。
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
啟動登入流程:
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 onResult(GetCredentialResponse result) { handleSignIn(result); } @Override public void onError(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"
}
}
在沒有憑證可用時處理例外狀況
在某些情況下,使用者可能沒有可用的憑證,或是未獲准使用可用的憑證。如果系統叫用 getCredential()
但找不到憑證,會傳回 NoCredentialException
。如果發生這種情況,您的程式碼應處理 NoCredentialException
例項。
Kotlin
try {
val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
Java
try {
Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
Log.e("CredentialManager", "No credential available", e);
}
在 Android 14 以上版本中,您可以在呼叫 getCredential()
前使用 prepareGetCredential()
方法,縮短顯示帳戶選取器的延遲時間。
Kotlin
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
<getPublicKeyCredentialOption>,
<getPasswordOption>
)
)
}
Java
GetCredentialResponse response = credentialManager.prepareGetCredential(
new GetCredentialRequest(
Arrays.asList(
new PublicKeyCredentialOption(),
new PasswordOption()
)
)
);
prepareGetCredential()
方法不會叫用 UI 元素。它只能協助您執行準備工作,以便之後透過 getCredential()
API 啟動其餘的憑證取得作業 (涉及 UI)。
快取的資料會在 PrepareGetCredentialResponse
物件中傳回。如果已有憑證,系統會快取結果,接著您可以在稍後啟動其餘的 getCredential()
API,執行含有快取資料的帳戶選取器。
註冊流程
建立密碼金鑰
如果想為使用者提供註冊密碼金鑰的選項,並允許他們使用密碼金鑰重新驗證,請使用 CreatePublicKeyCredentialRequest
物件註冊使用者憑證:
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": "abc123",
"rp": {
"name": "Credential Manager example",
"id": "credential-manager-test.example.com"
},
"user": {
"id": "def456",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [
{"id": "ghi789", "type": "public-key"},
{"id": "jkl012", "type": "public-key"}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
設定 authenticatorAttachment 的值
authenticatorAttachment
參數只能在建立憑證時設定。您可以指定 platform
或 cross-platform
,也可以不指定任何值。大多數情況下,我們會建議不要指定任何值。
platform
:如要註冊使用者目前的裝置,或提示密碼使用者在登入後升級為使用密碼金鑰,請將authenticatorAttachment
設為platform
。cross-platform
:這是註冊多重憑證時常用的值。如果使用密碼金鑰,則不會用到這個值。- 不指定值:如果想讓使用者能夠在慣用裝置上 (例如透過帳戶設定) 建立密碼金鑰,當使用者選擇新增密碼金鑰時,請勿指定
authenticatorAttachment
參數。大多數情況下,不指定參數是最佳選擇。
避免建立重複的密碼金鑰
在選用的 excludeCredentials
陣列中列出憑證 ID,以免在同一個密碼金鑰供應器中已存在密碼金鑰時,建立新的密碼金鑰。
處理 JSON 回應
下列程式碼片段顯示建立公開金鑰憑證的 JSON 回應範例。進一步瞭解如何處理傳回的公開金鑰憑證。
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
驗證用戶端資料 JSON 的來源
origin
代表要求的來源應用程式或網站,密碼金鑰會使用這個參數防範網路釣魚攻擊。應用程式的伺服器必須依據核准的應用程式和網站許可清單,檢查用戶端資料來源。如果伺服器收到不明來源的應用程式或網站發出的要求,則應拒絕要求。
如果是網頁,origin
會反映憑證登入網站的相同網站來源。例如,假設網址為 https://www.example.com:8443/store?category=shoes#athletic
,則 origin
為 https://www.example.com:8443
。
如果是 Android 應用程式,使用者代理程式會自動將 origin
設為呼叫應用程式的簽章。此簽章的驗證結果必須與應用程式伺服器上的許可清單比對相符,才能驗證密碼金鑰 API 的呼叫端。Android origin
是衍生自 APK 簽署憑證 SHA-256 雜湊的 URI,例如:
android:apk-key-hash:<sha256_hash-of-apk-signing-cert>
執行下列終端機指令,即可從 KeyStore 找到簽署憑證的 SHA-256 雜湊:
keytool -list -keystore <path-to-apk-signing-keystore>
SHA-256 雜湊採用半形冒號分隔的十六進位格式 (91:F7:CB:F9:D6:81…
),Android origin
值則採用 Base64url 編碼。請參考以下 Python 範例,瞭解如何將雜湊格式轉換為以半形冒號分隔的相容十六進位格式:
import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
將 fingerprint
的值替換成您自己的值。結果範例如下:
android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU
這樣一來,您就可以加入該字串做為伺服器上的許可來源。如果有多個簽署憑證 (例如用於偵錯和發布的憑證),或有多個應用程式,請重複上述程序,接受所有這些來源做為伺服器上的有效來源。
儲存使用者密碼
如果使用者在應用程式中提供驗證流程的使用者名稱和密碼,您可以註冊使用者憑證。這個憑證可以用來驗證使用者。方法是建立 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) 一節。
支援使用密碼金鑰端點已知網址的密碼管理工具
為了與密碼和 Credential Manager 工具完美整合,並提升日後的相容性,建議您新增密碼金鑰端點已知網址的支援功能。這是開放式的通訊協定,供聯盟中各方正式宣傳自家對密碼金鑰的支援,並提供註冊及管理密碼金鑰的直接連結。
- 如果您是
https://example.com
的依賴方,且擁有網站和 Android 和 iOS 應用程式,則已知網址會是https://example.com/.well-known/passkey-endpoints
。 使用者查詢該網址時,回應應採用下列結構定義:
{ "enroll": "https://example.com/account/manage/passkeys/create" "manage": "https://example.com/account/manage/passkeys" }
如要直接在應用程式中 (而非在網頁上) 開啟這個連結,請使用 Android 應用程式連結。
詳情請參閱 GitHub 的密碼金鑰端點已知網址說明文件。
顯示建立密碼金鑰的供應商,協助使用者管理密碼金鑰
使用者在管理與特定應用程式相關聯的多個密碼金鑰時,會面臨一個挑戰,就是要找出要編輯或刪除的正確密碼金鑰。為協助解決這個問題,建議應用程式和網站在應用程式設定畫面的密碼金鑰清單中,加入其他資訊,例如建立憑證的供應商、建立日期和上次使用日期。您可以檢查與對應密碼金鑰相關聯的 AAGUID,取得供應商資訊。AAGUID 可在密碼金鑰的驗證器資料中找到。
舉例來說,如果使用者透過 Google 密碼管理工具在 Android 裝置上建立密碼金鑰,RP 就會收到 AAGUID,如下所示:「ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4」。依賴方可以在密碼金鑰清單中加上註解,指出密碼金鑰是使用 Google 密碼管理工具建立的。
如要將 AAGUID 對應至密碼金鑰供應器,RP 可以使用由社群提供的 AAGUID 存放區。在清單中查詢 AAGUID,找出密碼金鑰提供者名稱和圖示。
進一步瞭解 AAGUID 整合。
排解常見錯誤
請參閱 Credential Manager 疑難排解指南,瞭解常見的錯誤代碼、說明及其原因。
其他資源
如要進一步瞭解 Credential Manager API 和密碼金鑰,請參閱下列資源:
- 密碼金鑰使用者體驗指南
- 影片:如何使用密碼金鑰支援功能降低 Android 應用程式對密碼的依賴
- 程式碼研究室:瞭解如何在 Android 應用程式中使用 Credential Manager API 簡化驗證流程
- 範例應用程式:CredentialManager