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)