การโอนข้อมูลเข้าสู่ระบบ

API การโอนข้อมูลเข้าสู่ระบบของ Credential Manager ช่วยให้โอนข้อมูลเข้าสู่ระบบของผู้ใช้ระหว่างผู้ให้บริการข้อมูลเข้าสู่ระบบในอุปกรณ์เดียวกันได้อย่างปลอดภัย คู่มือนี้จะอธิบายรายละเอียดเกี่ยวกับวิธีที่ผู้ให้บริการข้อมูลเข้าสู่ระบบใน Android สามารถผสานรวมกับ API ที่ไลบรารี androidx.credentials:providerevents จัดหาให้ ฟีเจอร์นี้รองรับ รหัสผ่าน รหัสผ่านแบบไม่ต้องจำ ข้อมูลที่อยู่ และช่องที่กำหนดเองโดยใช้ รูปแบบการแลกเปลี่ยนข้อมูลเข้าสู่ระบบ FIDO (CXF) ที่ได้มาตรฐาน

แนวคิดหลัก

เฟรมเวิร์กการโอนข้อมูลเข้าสู่ระบบช่วยให้การโอนข้อมูลเข้าสู่ระบบแบบเพียร์ทูเพียร์ในอุปกรณ์เดียวกันเป็นเรื่องง่ายโดยไม่ต้องเปิดเผยข้อมูลเข้าสู่ระบบดิบต่อระบบปฏิบัติการ Android หรือแอปที่ไม่ได้ตรวจสอบสิทธิ์

เฟรมเวิร์กกำหนดบทบาทหลัก 2 บทบาท ดังนี้

  • ผู้ส่งออก (ผู้ให้บริการต้นทาง): ผู้ให้บริการข้อมูลเข้าสู่ระบบที่ถือข้อมูลเข้าสู่ระบบของผู้ใช้ในปัจจุบัน โดยจะลงทะเบียนข้อมูลเมตาเกี่ยวกับบัญชีที่ส่งออกได้ (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)

ใช้ผู้ส่งออก

หากต้องการอนุญาตให้ผู้ใช้ส่งออกข้อมูลเข้าสู่ระบบจากแอปของคุณไปยังผู้ให้บริการข้อมูลเข้าสู่ระบบอื่นๆ ในอุปกรณ์ ให้ใช้บทบาทผู้ส่งออก

ลงทะเบียนรายการส่งออก

เมื่อผู้ให้บริการข้อมูลเข้าสู่ระบบมีการเปลี่ยนแปลง (เช่น ผู้ใช้ลงชื่อเข้าใช้ เพิ่มข้อมูลเข้าสู่ระบบ หรือแก้ไขบัญชี) ให้ลงทะเบียนหรืออัปเดตรายการ ExportEntry โดยใช้ ProviderEventsManager.registerExport()

ExportEntry แต่ละรายการต้องมีข้อมูลต่อไปนี้

  • id: ตัวระบุสตริงลับที่สร้างขึ้นแบบสุ่มซึ่งแสดงรายการส่งออกนี้โดยไม่ซ้ำกัน คุณต้องเก็บรักษารหัสนี้ไว้อย่างปลอดภัย เนื่องจากคุณจะต้องใช้รหัสนี้ ในภายหลังเพื่อยืนยันคำขอโอนที่เข้ามา
  • accountDisplayName: ป้ายกำกับบัญชีที่ไม่บังคับ (เช่น "Personal Account")
  • userDisplayName: ตัวระบุหลักของผู้ใช้ (เช่น "alice@example.com")
  • icon: ไอคอน Bitmap ที่แสดงผู้ให้บริการหรือบัญชี (ไลบรารีจะปรับขนาดเป็น PNG ขนาด 32x32 โดยอัตโนมัติ)
  • 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)
    }
}

ประกาศกิจกรรมผู้ส่งออกในไฟล์ Manifest

เมื่อผู้ใช้เลือก ExportEntry ใน UI ตัวเลือกของระบบ ระบบจะเปิดใช้ Activity การจัดการที่กำหนด ประกาศกิจกรรมนี้ด้วยการดำเนินการ Intent และสคีม 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>

จัดการ Intent การโอนในกิจกรรม

ใน 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: แสดงขึ้นเมื่อการลงทะเบียน หรือการล้างข้อมูลล้มเหลวเนื่องจากปัญหาการตั้งค่าผู้ให้บริการ (เช่น 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: ผู้ใช้ปิด UI ตัวเลือกหรือยกเลิกกิจกรรมการส่งออก (Activity.RESULT_CANCELED)
  • ImportCredentialsNoExportOptionException: ไม่มีรายการส่งออกที่ลงทะเบียนไว้ตรงกับประเภทข้อมูลเข้าสู่ระบบที่ขอ หรือผู้ส่งออกที่เลือกแสดงข้อยกเว้นการปฏิเสธ
  • ImportCredentialsProviderConfigurationException: ข้อผิดพลาดในการกำหนดค่า (เช่น ชุด credentialTypes ว่างเปล่าใน ImportCredentialsRequest หรือไม่มีสิทธิ์)
  • 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" SSID และวลีรหัสผ่านของเครือข่าย Wi-Fi

ตัวจับคู่ WASM (WebAssembly) ที่กำหนดเอง (ขั้นสูง)

โดยค่าเริ่มต้น การเรียกใช้ RegisterExportRequest.create(context, entries) จะรวม credential_transfer_matcher.wasm เริ่มต้นของระบบจากชิ้นงานไลบรารี ซึ่งจะกรองรายการตามการตัดกันของ supportedCredentialTypes เท่านั้น

หากผู้ให้บริการข้อมูลเข้าสู่ระบบของคุณต้องใช้ตรรกะการจับคู่ที่ซับซ้อน (เช่น การตรวจสอบความสามารถแบบไดนามิกหรือการกรองแบบมีเงื่อนไขตามช่องที่กำหนดเอง) คุณสามารถคอมไพล์โมดูล WebAssembly ของคุณเองที่ตรงกับ API การโอนข้อมูลเข้าสู่ระบบ WASM และส่งอาร์เรย์ไบต์ดิบโดยตรงได้

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