Credential Manager 的憑證轉移 API 可讓憑證提供者在同一部裝置上安全地轉移使用者憑證。本指南詳細說明 Android 上的憑證提供者如何與 androidx.credentials:providerevents 程式庫提供的 API 整合。這項功能支援密碼、密碼金鑰、地址資訊和自訂欄位,並使用標準化的 FIDO 憑證交換格式 (CXF)。
核心概念
憑證轉移架構可讓您在同一部裝置上進行點對點憑證轉移,且不會向 Android OS 或未經驗證的應用程式公開原始憑證。
這個架構定義了兩個主要角色:
- 匯出者 (來源提供者):目前持有使用者憑證的憑證提供者。預先向系統註冊可匯出帳戶的相關中繼資料 (
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,可做為這個匯出項目的專屬代表。請務必妥善保存這個 ID,稍後您需要這個 ID 來驗證轉移要求。accountDisplayName:選用帳戶標籤 (例如"Personal Account")。userDisplayName:使用者的主要 ID (例如"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) } }
在資訊清單檔案中宣告匯出器活動
使用者在系統選取器 UI 中選取 ExportEntry 時,系統會啟動指定的處理 Activity。使用必要意圖動作和內容 URI 配置宣告這項活動:
<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>
在活動中處理轉移意圖
在 CredentialExportActivity 中,使用 IntentHandler.retrieveProviderImportCredentialsRequest(intent) 剖析要求、驗證呼叫應用程式和 credId、執行任何必要的生物特徵辨識驗證,然後將 FIDO CXF 酬載寫回提供的內容 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:由於供應商設定問題 (例如ExportEntry中的supportedCredentialTypes為空,或在不支援的 OS 層級上呼叫),導致註冊或清除作業失敗時擲回。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:使用者關閉選取器 UI 或取消匯出活動 (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" |
安全殼層公開和私密金鑰組。 |
CREDENTIAL_TYPE_TOTP |
"totp" |
限時動態密碼 (雙因素驗證) 密鑰。 |
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)