Chuyển thông tin xác thực

Credential Transfer API 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 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 cách sử dụng Định dạng trao đổi thông tin xác thực (CXF) theo tiêu chuẩn FIDO.

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ộ 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 2 vai trò chính:

  • Trình xuất (nhà cung cấp nguồn): Một trình cung cấp thông tin xác thực hiện đang lưu giữ thông tin đăng nhập của người dùng. Thư viện 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 (trình cung cấp ứng dụng hoặc trình hướng dẫn thiết lập): Trình cung cấp thông tin đăng nhập hoặc trình hướng dẫn thiết lập sẽ bắt đầu một yêu cầu nhập (ImportCredentialsRequest) chỉ định những loại thông tin đăng nhập 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

Credentials Transfer API 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 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 đăng nhập từ ứng dụng của bạn sang các trình cung cấp thông tin đăng nhập khác trên thiết bị, hãy triển khai vai trò trình xuất.

Đăng ký các mục xuất

Khi trình cung cấp thông tin đăng nhập của bạn thay đổi (ví dụ: người dùng đăng nhập, thêm thông tin đăng nhập 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 yêu cầu:

  • id: Một 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 nhập xuất này. Bạn phải lưu trữ mã nhận dạng này một cách an toàn; bạn sẽ cần mã nhận dạng này sau này để xác minh các yêu cầu chuyển tiề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ượng Bitmap đại diện cho nhà cung cấp hoặc tài khoản (thư viện tự động điều chỉnh tỷ lệ thành PNG 32x32).
  • supportedCredentialTypes: Một tập hợp các hằng số chuỗi từ CredentialTypes biểu thị những loại mà mục này chứa.

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ẽ khởi chạy Activity xử lý được chỉ định của bạn. Khai báo hoạt động này bằng thao tác theo ý định và lược đồ URI nội dung bắt buộc:

<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>

Xử lý ý định chuyển trong hoạt động của bạn

Trong CredentialExportActivity, hãy dùng IntentHandler.retrieveProviderImportCredentialsRequest(intent) để phân tích cú pháp yêu cầu, xác minh ứng dụng gọi và credId, thực hiện mọi quy trình xác thực bằng sinh trắc học bắt buộc và ghi tải trọng FIDO CXF trở lại URI nội dung đã cung cấp:

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()
    }
}

Xuất thông tin đăng ký và xoá các trường hợp ngoại lệ

Tất cả các trường hợp ngoại lệ trong mô-đun này đều là lớp con của ImportCredentialsException, RegisterExportException hoặc ClearExportException.

Triển khai trình nhập

Để nhập thông tin đăng nhập vào ứng dụng (ví dụ: trong quá trình giới thiệu hoặc nhập nhà cung cấp), hãy triển khai vai trò trình nhập và bắt đầu quy trình bằng cách gọi ProviderEventsManager.importCredentials().

Tạo yêu cầu nhập và quy trình khởi chạy

Chỉ định CredentialTypesKnownExtensions 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 các ngoại lệ về luồng

Tất cả các trường hợp ngoại lệ trong mô-đun này đều là lớp con của ImportCredentialsException, RegisterExportException hoặc ClearExportException.

Các loại thông tin đăng nhập 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 được 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 khoá truy cập FIDO2 hoặc WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" Thông tin về địa chỉ bưu điện 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ã thông báo 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, mã số thuế hoặc thông tin hộ chiếu.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Các đườ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 về hộ chiếu và giấy tờ tuỳ thân khi đi lại.
CREDENTIAL_TYPE_PERSON_NAME "person-name" Thông tin nhận dạng và tên của 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ẽ liên kết credential_transfer_matcher.wasm mặc định của hệ thống từ các thành phần thư viện, chỉ lọc các mục dựa trên giao điểm của supportedCredentialTypes.

Nếu trình cung cấp thông tin đăng nhập của bạn yêu cầu logic so khớp phức tạp (ví dụ: kiểm tra khả 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 đăng nhập 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)