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

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ượng Bitmap đạ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:

  1. 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ột id (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ằng request.credId khớ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.
  2. 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ằng startActivityForResult, nên hoạt động của bạn có thể kiểm tra getCallingPackage() để 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.
  3. Kiểm tra request.callingAppInfo (thông tin ứng dụng mục tiêu): callingAppInfo xá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.

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.

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)