API Chuyển thông tin xác thực của Trình quản lý thông tin xác thực cho phép chuyển thông tin xác thực của người dùng một cách an toàn giữa các trình cung cấp thông tin xác thực trên cùng một thiết bị. Hướng dẫn này trình bày chi tiết cách các trình cung cấp thông tin xác thực trên Android có thể tích hợp với các API do thư viện androidx.credentials:providerevents cung cấp. Tính năng này hỗ trợ mật khẩu, khoá truy cập, thông tin địa chỉ và các trường tuỳ chỉnh bằng Định dạng trao đổi thông tin xác thực FIDO (CXF) chuẩn hoá.
Các khái niệm chính
Khung chuyển thông tin xác thực hỗ trợ việc chuyển thông tin xác thực ngang hàng trên cùng một thiết bị mà không làm lộ thông tin xác thực thô cho hệ điều hành Android hoặc các ứng dụng chưa được xác thực.
Khung này xác định hai vai trò chính:
- Bên xuất (nhà cung cấp nguồn): Nhà cung cấp thông tin xác thực hiện đang lưu giữ thông tin xác thực của người dùng. Ứng dụng này đăng ký trước siêu dữ liệu về các tài khoản có thể xuất (
ExportEntry) với hệ thống và phản hồi các yêu cầu chuyển khi người dùng chọn. - Trình nhập (nhà cung cấp ứng dụng hoặc trình hướng dẫn thiết lập): Nhà cung cấp thông tin xác thực hoặc trình hướng dẫn thiết lập bắt đầu yêu cầu nhập (
ImportCredentialsRequest) chỉ định loại thông tin xác thực và tiện ích mà trình nhập có thể nhận.
Khả năng tương thích với phiên bản Android
API Chuyển thông tin xác thực hoạt động trên các thiết bị chạy Android 8 (cấp độ API 26) trở lên.
Thêm phần phụ thuộc
Thêm phần phụ thuộc androidx.credentials:providerevents vào tệp build.gradle hoặc build.gradle.kts của mô-đun:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Tạo thực thể cho lớp bắt buộc
Tạo một thực thể của ProviderEventsManager bắt buộc.
val providerEventsManager = ProviderEventsManager.create(context)
Triển khai trình xuất
Để cho phép người dùng xuất thông tin xác thực từ ứng dụng của bạn sang các trình cung cấp thông tin xác thực khác trên thiết bị, hãy triển khai vai trò của trình xuất.
Đăng ký mục xuất
Khi nhà cung cấp thông tin xác thực của bạn thay đổi (ví dụ: người dùng đăng nhập, thêm thông tin xác thực hoặc sửa đổi tài khoản), hãy đăng ký hoặc cập nhật các mục ExportEntry bằng ProviderEventsManager.registerExport().
Mỗi ExportEntry cần có:
id: Giá trị nhận dạng chuỗi bí mật, được tạo ngẫu nhiên, đại diện duy nhất cho mục xuất này. Bạn phải lưu giữ mã nhận dạng này một cách an toàn; bạn sẽ cần mã này sau để xác minh các yêu cầu chuyển đến.accountDisplayName: Nhãn tài khoản không bắt buộc (ví dụ:"Personal Account").userDisplayName: Giá trị nhận dạng chính của người dùng (ví dụ:"alice@example.com").icon: Biểu tượngBitmapđại diện cho nhà cung cấp hoặc tài khoản (được thư viện tự động điều chỉnh theo tỷ lệ thành PNG 32x32).supportedCredentialTypes: Một tập hợp các hằng số chuỗi từCredentialTypesđại diện cho các loại mà mục này lưu giữ.
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) } }
Khai báo hoạt động của trình xuất trong tệp kê khai
Khi người dùng chọn ExportEntry của bạn trên giao diện người dùng của bộ chọn hệ thống, hệ thống sẽ chạy Activity xử lý mà bạn chỉ định. Khai báo hoạt động này bằng
hành động theo ý định bắt buộc và lược đồ URI nội dung.
Để bảo vệ khỏi các lệnh gọi trái phép và các cuộc tấn công của người đại diện nhầm lẫn, hãy khai báo android:requireContentUriPermissionFromCaller="write" trên Android 14 (cấp độ API 34) trở lên. Thao tác này hướng dẫn hệ điều hành Android xác minh rằng phương thức gọi bắt đầu khởi chạy có quyền ghi vào URI nội dung đích trước khi cho phép hoạt động bắt đầu, như trong ví dụ sau:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:requireContentUriPermissionFromCaller="write"
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>
Xử lý ý định chuyển trong hoạt động của bạn
Trong CredentialExportActivity, hãy sử dụng IntentHandler.retrieveProviderImportCredentialsRequest(intent) để phân tích cú pháp yêu cầu, xác minh thành phần gọi và siêu dữ liệu yêu cầu, thực hiện mọi quy trình xác thực sinh trắc học bắt buộc và ghi tải trọng FIDO CXF trở lại URI nội dung được cung cấp.
Xác thực yêu cầu
Vì hoạt động của bạn được xuất, hãy bảo vệ quy trình xuất thông tin xác thực bằng cách xác minh từng lớp sau:
- Xác minh số chỉ dùng một lần của phiên
credId(biện pháp phòng vệ chính): Khi đăng kýExportEntry, hãy tạo mộtid(số chỉ dùng một lần) ngẫu nhiên, có độ hỗn loạn cao bằng phương pháp mã hoá và liên kết số đó với một phiên ngắn hạn. Trong hoạt động của bạn, hãy xác minh rằngrequest.credIdkhớp với một số chỉ dùng một lần trong phiên đang hoạt động và chưa hết hạn mà nhà cung cấp của bạn đã phát hành trước đó. Vì các ứng dụng bên thứ ba không thể đoán được số chỉ dùng một lần này, nên điều này đảm bảo rằng ý định tương ứng với lựa chọn của người dùng thực trên bộ chọn hệ thống. - Xác minh
Activity.getCallingPackage()(kiểm tra phương thức gọi): Vì bộ chọn thông tin xác thực khởi chạy hoạt động của bạn bằngstartActivityForResult, nên hoạt động của bạn có thể kiểm tragetCallingPackage()để xác nhận rằng phương thức gọi trực tiếp khởi chạy giao diện người dùng là khung bộ chọn thông tin xác thực hệ thống đáng tin cậy (trên thiết bị Android có dịch vụ Google Play,"com.google.android.gms"). Từ chối yêu cầu nếu không nhận dạng được gói gọi. - Kiểm tra
request.callingAppInfo(thông tin ứng dụng mục tiêu):callingAppInfoxác định ứng dụng đích sẽ nhận thông tin đăng nhập đã nhập. Bạn có thể sử dụng thông tin này khi thấy phù hợp để đảm bảo tính minh bạch (chẳng hạn như hiển thị tên và biểu tượng của ứng dụng đích trong giao diện người dùng xác nhận) hoặc xác minh xem ứng dụng đích có đáng tin cậy hay không.
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() } }
Đăng ký xuất khẩu và các trường hợp ngoại lệ về thủ tục thông quan
Tất cả các ngoại lệ trong mô-đun này đều là lớp con của ImportCredentialsException, RegisterExportException hoặc ClearExportException.
RegisterExportProviderConfigurationExceptionhoặcClearExportProviderConfigurationException: Được gửi khi không đăng ký hoặc xoá được do vấn đề về việc thiết lập nhà cung cấp (ví dụ:supportedCredentialTypestrống trongExportEntryhoặc gọi trên một cấp hệ điều hành không được hỗ trợ).RegisterExportUnknownErrorExceptionhoặcClearExportUnknownErrorException: Đã xảy ra lỗi hệ thống hoặc lỗi bộ nhớ chưa được phân loại trong khi cập nhật sổ đăng ký xuất.
Triển khai trình nhập
Để nhập thông tin xác thực vào ứng dụng (ví dụ: trong quá trình làm quen với ứng dụng hoặc nhập thông tin của nhà cung cấp), hãy triển khai vai trò của trình nhập và bắt đầu flow bằng cách gọi ProviderEventsManager.importCredentials().
Tạo yêu cầu nhập và khởi chạy quy trình
Chỉ định CredentialTypes và KnownExtensions mà trình nhập của bạn hỗ trợ:
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) {}
Nhập ngoại lệ về quy trình
Tất cả các ngoại lệ trong mô-đun này đều là lớp con của ImportCredentialsException, RegisterExportException hoặc ClearExportException.
ImportCredentialsCancellationException: Người dùng đã đóng giao diện người dùng của bộ chọn hoặc huỷ hoạt động xuất (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: Không có mục xuất đã đăng ký nào khớp với các loại thông tin xác thực được yêu cầu hoặc trình xuất đã chọn gửi một ngoại lệ từ chối.ImportCredentialsProviderConfigurationException: Lỗi cấu hình (ví dụ:credentialTypestrống được đặt trongImportCredentialsRequesthoặc thiếu quyền).ImportCredentialsInvalidJsonException: Trình xuất đã trả về một gói dữ liệu JSON không đúng định dạng hoặc trống, không vượt qua được quy trình xác thực yêu cầu.ImportCredentialsSystemErrorException: Đã xảy ra lỗi hệ thống Android nội bộ hoặc lỗi chuyển Binder trong quá trình nhập.ImportCredentialsUnknownCallerException: Khung không thể xác minh ứng dụng gọi điện.ImportCredentialsUnknownErrorException: Lỗi không phân loại được hoặc không mong muốn trong quy trình nhập.
Các loại thông tin xác thực và tiện ích được hỗ trợ
Đối tượng androidx.credentials.providerevents.transfer.CredentialTypes xác định các hằng số chuỗi chuẩn hoá tương ứng với các loại mục FIDO CXF:
| Hằng số | Giá trị (loại cxf) |
Mô tả |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Thông tin đăng nhập bằng tên người dùng và mật khẩu. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Thông tin xác thực khoá công khai của khoá truy cập FIDO2 hoặc WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Thông tin về địa chỉ bưu chính hoặc địa chỉ giao hàng để tự động điền vào biểu mẫu. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Khoá và mã truy cập API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Thông tin thanh toán bằng thẻ tín dụng và thẻ ghi nợ. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Nhóm tuỳ chỉnh hoặc trường do người dùng xác định. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Thông tin chi tiết về giấy phép lái xe. |
CREDENTIAL_TYPE_FILE |
"file" |
Siêu dữ liệu và phần giữ chỗ tham chiếu cho tệp nhị phân. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Mật khẩu an toàn do máy tạo. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Thẻ căn cước, số an sinh xã hội (SSN), mã số thuế (TIN) hoặc thông tin tham chiếu hộ chiếu. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Đường liên kết logic trỏ đến một mục khác trong tải trọng. |
CREDENTIAL_TYPE_NOTE |
"note" |
Ghi chú bảo mật do người dùng xác định (chuỗi UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Thông tin chi tiết về hộ chiếu/giấy tờ thông hành. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Thông tin nhận dạng và đặt tên cho người. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Cặp khoá công khai và khoá riêng tư SSH. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Mã bí mật của mật khẩu một lần dựa trên thời gian (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
SSID và cụm mật khẩu của mạng Wi-Fi. |
Trình so khớp WASM (WebAssembly) tuỳ chỉnh (Nâng cao)
Theo mặc định, việc gọi RegisterExportRequest.create(context, entries) sẽ gói credential_transfer_matcher.wasm mặc định của hệ thống từ các thành phần thư viện, giúp lọc các mục nhập hoàn toàn dựa trên giao điểm của supportedCredentialTypes.
Nếu nhà cung cấp thông tin xác thực của bạn yêu cầu logic so khớp phức tạp (ví dụ: kiểm tra chức năng động hoặc lọc có điều kiện dựa trên các trường tuỳ chỉnh), bạn có thể biên dịch mô-đun WebAssembly của riêng mình khớp với API chuyển thông tin xác thực WASM và truyền trực tiếp mảng byte thô:
// 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)