Credential Manager 的凭据传输 API 支持在凭据提供方之间安全地在同一设备上转移用户凭据。本指南详细介绍了
Android 上的凭据提供方如何与
androidx.credentials:providerevents 库提供的 API 集成。此功能使用
标准化的 FIDO 凭据交换格式 (CXF)支持
密码、通行密钥、地址信息和自定义字段。
核心概念
凭据传输框架有助于在同一设备上进行对等凭据传输,而不会向 Android 操作系统或未经身份验证的应用公开原始凭据。
该框架定义了两个主要角色:
- 导出器(来源提供方): 当前持有用户凭据的凭据提供方。它会向系统预注册有关可用可导出
账号 (
ExportEntry) 的元数据,并在用户选择时响应传输 请求。 - 导入器(客户端提供方或设置向导): 凭据提供方或
设置向导,用于发起导入请求
(
ImportCredentialsRequest),指定它可以接收的凭据类型和 扩展程序。
Android 版本兼容性
凭据传输 API 适用于搭载 Android 8(API 级别 26)及更高版本的设备。
添加依赖项
将 androidx.credentials:providerevents 依赖项添加到模块的
build.gradle 或 build.gradle.kts:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
实例化所需类
创建所需 ProviderEventsManager 的实例。
val providerEventsManager = ProviderEventsManager.create(context)
实现导出器
如需允许用户将凭据从您的应用导出到设备上的其他凭据提供方,请实现导出器角色。
注册导出条目
当您的凭据提供方发生更改(例如,用户登录、添加凭据或修改账号)时,请使用 ProviderEventsManager.registerExport() 注册或更新 ExportEntry 项。
每个 ExportEntry 都需要:
id:一个秘密的随机生成的字符串标识符,用于唯一表示此导出条目。您必须安全地保留此 ID;您稍后需要使用它 来验证传入的传输请求。accountDisplayName:可选账号标签(例如"Personal Account")。userDisplayName:用户的主要标识符(例如"alice@example.com")。icon:表示提供方或账号的Bitmap图标(由库自动缩放为 32x32 PNG)。supportedCredentialTypes:一组来自CredentialTypes的字符串常量,表示此条目包含的类型。
suspend fun registerMyProviderForExport( providerEventsManager: ProviderEventsManager, providerIcon: Bitmap, // Randomly generated and stored in encrypted storage secretEntryId: String ) { val entry = ExportEntry( id = secretEntryId, accountDisplayName = "MyProvider Personal", userDisplayName = "alice@example.com", icon = providerIcon, supportedCredentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD ) ) // RegisterExportRequest.create() attaches the default WASM matcher from assets val request = RegisterExportRequest.create(context, listOf(entry)) try { val response = providerEventsManager.registerExport(request) // Registration successful } catch (e: Exception) { // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException) } }
在清单文件中声明导出器 activity
当用户在系统选择器界面上选择您的 ExportEntry 时,系统会启动您指定的处理 Activity。使用强制性 intent 操作和内容 URI scheme 声明此 Activity:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:label="@string/export_activity_label">
<intent-filter>
// This intent action is required for Credential Manager to invoke this activity
<action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
</intent-filter>
</activity>
处理您的 activity 中的传输 intent
在 CredentialExportActivity 中,使用 IntentHandler.retrieveProviderImportCredentialsRequest(intent) 解析请求,验证调用应用和 credId,执行任何所需的生物识别身份验证,并将 FIDO CXF 载荷写回提供的 content URI:
class CredentialExportActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. Extract the transfer request from the incoming Intent val request: ProviderImportCredentialsRequest? = IntentHandler.retrieveProviderImportCredentialsRequest(intent) if (request == null) { finishWithError() return } // 2. Validate CallingAppInfo and secret `credId` val callingAppPackage = request.callingAppInfo.packageName val receivedCredId = request.credId if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) { // Secret ID mismatch or untrusted caller -> abort sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request")) return } // 3. Optional: Prompt user for Biometric / PIN authentication before exporting authenticateUserThenExport(request) } private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) { // ... Biometric prompt logic ... // Once authenticated, generate the FIDO CXF JSON string matching the requested types val cxfJsonPayload = buildFidoCxfJsonPayload( requestedTypes = request.request.credentialTypes, requestedExtensions = request.request.knownExtensions ) val response = ImportCredentialsResponse(cxfJsonPayload) // 4. Write the JSON payload to the Content URI and set Activity result IntentHandler.setImportCredentialsResponse( context = this, uri = request.uri, intent = intent, response = response ) setResult(Activity.RESULT_OK, intent) finish() } private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) { IntentHandler.setImportCredentialsException(intent, exception) setResult(Activity.RESULT_OK, intent) finish() } private fun verifySecretEntryId(credentialId: String): Boolean { // Check if credentialId matches what you stored when calling RegisterExportRequest return credentialId == getStoredSecretEntryId() } private fun isTrustedImporter(packageName: String): Boolean { // Implement any specific allowlisting / caller checks if required return true } private fun finishWithError() { setResult(Activity.RESULT_CANCELED) finish() } }
导出注册和清除异常
此模块中的所有异常都是
ImportCredentialsException、RegisterExportException或
ClearExportException的子类。
RegisterExportProviderConfigurationException或ClearExportProviderConfigurationException:由于提供方设置问题(例如,supportedCredentialTypes在ExportEntry中为空,或在不受支持的 操作系统层级上调用)导致注册 或清除失败时抛出。RegisterExportUnknownErrorException或ClearExportUnknownErrorException:更新导出注册表时发生未分类的系统或存储 错误。
实现导入器
如需将凭据导入到您的应用中(例如,在用户入门或提供方
导入期间),请实现导入器角色,并通过调用
ProviderEventsManager.importCredentials()来启动流程。
构建导入请求并启动流程
指定导入器支持的 CredentialTypes 和 KnownExtensions:
suspend fun startCredentialImport( activityContext: Context, providerEventsManager: ProviderEventsManager ) { val importRequest = ImportCredentialsRequest( credentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_NOTE ), knownExtensions = setOf( KnownExtensions.KNOWN_EXTENSION_SHARED ) ) try { // Launches the system Selector UI; suspends until user selects a provider and completes transfer val response = providerEventsManager.importCredentials(activityContext, importRequest) // 1. Inspect the source exporter's package info val exporterPackageName = response.callingAppInfo.packageName // 2. Parse the FIDO CXF JSON string val cxfJsonString = response.response.responseJson parseAndSaveImportedCredentials(cxfJsonString) } catch (e: ImportCredentialsException) { // Handle specific import exceptions (e.g., ImportCredentialsCancellationException) handleImportFailure(e) } } private fun parseAndSaveImportedCredentials(cxfJsonString: String) { val rootJson = JSONObject(cxfJsonString) // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification: // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html } // Helper function to make it compile private fun handleImportFailure(e: ImportCredentialsException) {}
导入流程异常
此模块中的所有异常都是
ImportCredentialsException、RegisterExportException或
ClearExportException的子类。
ImportCredentialsCancellationException:用户关闭了选择器界面或取消了导出 activity (Activity.RESULT_CANCELED)。ImportCredentialsNoExportOptionException:没有注册的导出条目与请求的凭据类型匹配,或者所选导出器抛出了拒绝异常。ImportCredentialsProviderConfigurationException:配置错误 (例如,在ImportCredentialsRequest中设置的credentialTypes为空或缺少权限)。ImportCredentialsInvalidJsonException:导出器返回了格式错误或为空的 JSON 载荷,导致请求验证失败。ImportCredentialsSystemErrorException:导入期间发生内部 Android 系统或 Binder 传输错误。ImportCredentialsUnknownCallerException:框架无法验证调用应用。ImportCredentialsUnknownErrorException:导入流程期间发生未分类或意外错误。
支持的凭据类型和扩展程序
androidx.credentials.providerevents.transfer.CredentialTypes 对象定义了与 FIDO CXF 项类型对应的标准化字符串常量:
| 常量 | 值(cxf 类型) |
说明 |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
用户名和密码登录凭据。 |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
FIDO2 或 WebAuthn 通行密钥公钥凭据。 |
CREDENTIAL_TYPE_ADDRESS |
"address" |
用于表单自动填充的邮寄地址或送货地址信息。 |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
API 访问密钥和令牌。 |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
信用卡和借记卡支付信息。 |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
自定义分组或用户定义的字段。 |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
驾照详细信息。 |
CREDENTIAL_TYPE_FILE |
"file" |
二进制文件的元数据和占位符引用。 |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
机器生成的安全密码。 |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
国民身份证、SSN、TIN 或护照引用。 |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
指向载荷中另一项的逻辑链接。 |
CREDENTIAL_TYPE_NOTE |
"note" |
用户定义的安全备注(UTF-8 字符串)。 |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
护照旅行证件详细信息。 |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
个人身份和命名详细信息。 |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
SSH 公钥和私钥对。 |
CREDENTIAL_TYPE_TOTP |
"totp" |
基于时间的一次性密码 (2FA) 密钥。 |
CREDENTIAL_TYPE_WIFI |
"wifi" |
Wi-Fi 网络 SSID 和密码。 |
自定义 WASM (WebAssembly) 匹配器(高级)
默认情况下,调用 RegisterExportRequest.create(context, entries) 会捆绑库资产中的系统默认 credential_transfer_matcher.wasm,该资产仅根据 supportedCredentialTypes 的交集过滤条目。
如果您的凭据提供方需要复杂的匹配逻辑(例如,动态功能检查或基于自定义字段的条件过滤),您可以编译自己的 WebAssembly 模块,使其与 WASM 凭据传输 API 匹配,并直接传递原始字节数组:
// Loading a custom WASM matcher val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() } val customRequest = RegisterExportRequest( entries = myEntries, exportMatcher = customMatcherBytes ) providerEventsManager.registerExport(customRequest)