凭据管理器是一个 Jetpack API,它通过单个 API 支持多种登录方法,例如用户名和密码、通行密钥以及联合登录解决方案(如 Google 登录),从而简化了开发者的集成工作。
此外,对于用户而言,凭据管理器统一了各种身份验证方法的登录界面,使用户无论选择哪种方法,都能更清晰、更轻松地登录应用。
本页面介绍了通行密钥的概念,以及使用凭据管理器 API 实现客户端身份验证解决方案(包括通行密钥)的步骤。还有一个单独的常见问题解答页面,其中提供了更详细、具体问题的答案。
关于通行密钥
通行密钥是更安全、更简单的密码替代方案。借助通行密钥,用户可以使用生物识别传感器(例如指纹或面部识别)、PIN 或图案登录应用和网站。这提供了一种无缝的登录体验,让您的用户无需记住用户名或密码。
通行密钥依赖于 WebAuthn(Web 身份验证),这是 FIDO 联盟和万维网联盟 (W3C) 共同开发的标准。WebAuthn 使用公钥加密来验证用户身份。用户登录的网站或应用可以看到并存储公钥,但绝不会看到私钥。私钥被安全地保密。而且,由于密钥是唯一的并与网站或应用绑定,因此通行密钥无法被网络钓鱼,从而进一步提高了安全性。
凭据管理器允许用户创建通行密钥并将其存储在Google 密码管理工具中。
阅读使用通行密钥进行用户身份验证,获取关于如何使用凭据管理器实现无缝通行密钥身份验证流程的指导。
前提条件
要使用凭据管理器,请完成本节中的步骤。
使用最新平台版本
凭据管理器支持 Android 4.4(API 级别 19)及更高版本。
向您的应用添加依赖项
将以下依赖项添加到您的应用模块的构建脚本中
implementation(libs.androidx.credentials)
// optional - needed for credentials support from play services, for devices running
// Android 13 and below.
implementation(libs.androidx.credentials.play.services.auth)
了解更多关于如何缩减、混淆和优化您的应用的信息。
添加对数字资产链接的支持
要为您的 Android 应用启用通行密钥支持,请将您的应用与您拥有的网站相关联。您可以通过完成以下步骤声明此关联:
创建一个数字资产链接 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
字段是一个对象,用于指定声明适用的资产。以下字段标识一个网站:命名空间
web
网站
网站的网址,格式为
https://domain[:optional_port]
;例如,https://www.example.com
。domain 必须是完全限定的,并且在使用 HTTPS 的 443 端口时必须省略 optional_port。
一个
site
目标只能是根域:您无法将应用关联限制到特定的子目录。不要在网址中包含路径,例如末尾的斜杠。不认为子域匹配:也就是说,如果您将 domain 指定为
www.example.com
,则域www.counter.example.com
不与您的应用关联。以下字段标识一个 Android 应用:
命名空间
android_app
package_name
应用清单中声明的包名。例如, com.example.android
sha256_cert_fingerprints
您的应用签名证书的 SHA256 指纹。 在登录域的以下位置托管数字资产链接 JSON 文件:
https://domain[:optional_port]/.well-known/assetlinks.json
例如,如果您的登录域是
signin.example.com
,则将 JSON 文件托管在https://signin.example.com/.well-known/assetlinks.json
。数字资产链接文件的 MIME 类型必须是 JSON。确保服务器在响应中发送
Content-Type: application/json
标头。确保您的主机允许 Google 检索您的数字资产链接文件。如果您有
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" />
如果您通过凭据管理器使用密码登录,请遵循此步骤在清单中配置数字资产链接。如果您只使用通行密钥,则不需要此步骤。
在 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
配置凭据管理器
要配置和初始化 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 时,将显示带有可用凭据的凭据管理器底部工作表,并且不再需要显示自动填充的用户名或密码填充对话框。类似地,也不需要显示自动填充的密码保存对话框,因为应用将请求凭据管理器 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
代表请求来源的应用或网站,并被通行密钥用于防范网络钓鱼攻击。您的应用服务器需要根据已批准应用和网站的允许列表检查客户端数据来源。如果服务器收到来自未知来源的应用或网站的请求,则应拒绝该请求。
在 Web 场景中,origin
反映了凭据登录的同站来源。例如,给定 URL 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>
可以通过运行以下终端命令找到密钥库中签名证书的 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 密码管理工具中通行密钥的安全性。
自动为用户创建通行密钥
如果用户没有通行密钥,您可以在他们下次使用密码管理工具中保存的密码登录时自动为他们创建一个。通过在请求公共凭据时设置 isConditionalCreateRequest
字段来实现:
CreatePublicKeyCredentialRequest(
// other parameters
isConditionalCreateRequest: Boolean = true
)
当用户登录时,会自动创建通行密钥并将其存储在用户选择的密码管理工具中。如果使用 Google 密码管理工具,用户必须使用(通过凭据管理器或自动填充)密码管理工具中保存的密码。用户在创建此通行密钥时会收到通知,并且可以前往密码管理工具进行管理。
此功能需要版本 1.6.0-alpha01 或更高版本。
通过通行密钥端点已知 URL 添加对密码管理工具的支持
为了实现无缝集成以及与密码和凭据管理工具的未来兼容性,我们建议添加对通行密钥端点已知 URL 的支持。这是一个开放协议,供相关方正式宣传其对通行密钥的支持,并提供通行密钥注册和管理的直接链接。
- 对于位于
https://example.com
的依赖方(拥有网站以及 Android 和 iOS 应用),已知 URL 将是https://example.com/.well-known/passkey-endpoints
。 查询该 URL 时,响应应使用以下 schema:
{ "enroll": "https://example.com/account/manage/passkeys/create" "manage": "https://example.com/account/manage/passkeys" }
要让此链接直接在您的应用中打开而不是在网页上打开,请使用 Android 应用链接。
更多详细信息可在 GitHub 上的通行密钥端点已知 URL 解释器中找到。
通过显示创建通行密钥的提供商,帮助用户管理通行密钥
用户在管理与给定应用关联的多个通行密钥时面临的一个挑战是识别用于编辑或删除的正确通行密钥。为解决此问题,建议应用和网站在应用的设置屏幕上的通行密钥列表中包含附加信息,例如创建凭据的提供商、创建日期和上次使用日期。提供商信息是通过检查与相应通行密钥关联的 AAGUID 获得的。AAGUID 可以作为通行密钥身份验证器数据的一部分找到。
例如,如果用户使用 Google 密码管理工具在 Android 设备上创建通行密钥,则 RP 会收到一个类似于“ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4”的 AAGUID。依赖方可以在通行密钥列表中注释通行密钥,以表明它是使用 Google 密码管理工具创建的。
要将 AAGUID 映射到通行密钥提供商,RP 可以使用一个社区来源的 AAGUID 存储库。在列表中查找 AAGUID,以找到通行密钥提供商名称和图标。
阅读更多关于 AAGUID 集成的信息。
排查常见错误
有关常见错误代码、描述及其原因的信息,请参阅凭据管理器问题排查指南。
其他资源
要了解更多关于凭据管理器 API 和通行密钥的信息,请查看以下资源:
- 通行密钥用户体验指南
- 视频:如何通过通行密钥支持减少 Android 应用对密码的依赖
- Codelab:学习如何在 Android 应用中使用凭据管理器 API 简化身份验证旅程
- 示例应用:CredentialManager