API การโอนข้อมูลเข้าสู่ระบบของ Credential Manager ช่วยให้โอนข้อมูลเข้าสู่ระบบของผู้ใช้ระหว่างผู้ให้บริการข้อมูลเข้าสู่ระบบได้อย่างปลอดภัยในอุปกรณ์เดียวกัน คู่มือนี้จะอธิบายรายละเอียดเกี่ยวกับวิธีที่ผู้ให้บริการข้อมูลเข้าสู่ระบบใน Android สามารถผสานรวมกับ API ที่จัดทำโดยไลบรารี androidx.credentials:providerevents ฟีเจอร์นี้รองรับรหัสผ่าน พาสคีย์ ข้อมูลที่อยู่ และฟิลด์ที่กำหนดเองโดยใช้รูปแบบการแลกเปลี่ยนข้อมูลเข้าสู่ระบบ FIDO (CXF) ที่ได้มาตรฐาน
แนวคิดหลัก
เฟรมเวิร์กการโอนข้อมูลเข้าสู่ระบบช่วยอำนวยความสะดวกในการโอนข้อมูลเข้าสู่ระบบแบบเพียร์ทูเพียร์ ในอุปกรณ์เดียวกันโดยไม่เปิดเผยข้อมูลเข้าสู่ระบบดิบต่อระบบปฏิบัติการ Android หรือ แอปที่ไม่ได้รับการตรวจสอบสิทธิ์
เฟรมเวิร์กกำหนดบทบาทหลัก 2 บทบาท ได้แก่
- ผู้ส่งออก (ผู้ให้บริการต้นทาง): ผู้ให้บริการข้อมูลเข้าสู่ระบบที่ปัจจุบันถือ
ข้อมูลเข้าสู่ระบบของผู้ใช้ โดยจะลงทะเบียนข้อมูลเมตาล่วงหน้าเกี่ยวกับบัญชีที่ส่งออกได้ (
ExportEntry) กับระบบ และตอบกลับคำขอโอนเมื่อผู้ใช้เลือก - ผู้นำเข้า (ผู้ให้บริการไคลเอ็นต์หรือวิซาร์ดการตั้งค่า): ผู้ให้บริการข้อมูลเข้าสู่ระบบหรือ
วิซาร์ดการตั้งค่าที่เริ่มคำขอนำเข้า
(
ImportCredentialsRequest) โดยระบุประเภทข้อมูลเข้าสู่ระบบและ ส่วนขยายที่รับได้
ความเข้ากันได้กับเวอร์ชัน Android
Credentials Transfer API ใช้งานได้ในอุปกรณ์ที่ใช้ Android 8 (ระดับ API 26) ขึ้นไป
เพิ่มทรัพยากร Dependency
เพิ่มทรัพยากร Dependency 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 เนื้อหาที่จำเป็น
หากต้องการป้องกันการเรียกใช้ที่ไม่ได้รับอนุญาตและการโจมตีแบบสับสนในหน้าที่ ให้ประกาศ
android:requireContentUriPermissionFromCaller="write" ใน Android 14 (ระดับ API
34) ขึ้นไป ซึ่งจะสั่งให้ระบบปฏิบัติการ Android ตรวจสอบว่าผู้เรียกที่เริ่มการเปิดใช้มีสิทธิ์เขียนไปยัง URI ของเนื้อหาปลายทางก่อนที่จะอนุญาตให้กิจกรรมเริ่มต้น ดังที่แสดงในตัวอย่างต่อไปนี้
<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>
จัดการ Intent การโอนในกิจกรรม
ใน CredentialExportActivity ให้ใช้
IntentHandler.retrieveProviderImportCredentialsRequest(intent) เพื่อแยกวิเคราะห์
คำขอ ยืนยันคอมโพเนนต์ที่เรียกใช้และข้อมูลเมตาของคำขอ ดำเนินการ
การตรวจสอบสิทธิ์ด้วยข้อมูลไบโอเมตริกที่จำเป็น และเขียนเพย์โหลด CXF ของ FIDO กลับไปยัง
URI เนื้อหาที่ระบุ
ตรวจสอบสิทธิ์คำขอ
เนื่องจากระบบจะส่งออกกิจกรรมของคุณ ให้ปกป้องขั้นตอนการส่งออกข้อมูลเข้าสู่ระบบโดย ยืนยันแต่ละเลเยอร์ต่อไปนี้
- ยืนยัน
credIdnonce ของเซสชัน (การป้องกันหลัก): เมื่อลงทะเบียนExportEntryให้สร้างid(nonce) ที่สุ่มแบบเข้ารหัส ที่มีเอนโทรปีสูง แล้วเชื่อมโยงกับเซสชันที่มีอายุสั้น ในกิจกรรมของคุณ ให้ตรวจสอบว่าrequest.credIdตรงกับค่า Nonce ของเซสชันที่ใช้งานอยู่และยังไม่หมดอายุ ซึ่งผู้ให้บริการออกให้ก่อนหน้านี้ เนื่องจากแอปของบุคคลที่สาม ไม่สามารถคาดเดานอนซ์นี้ได้ จึงมั่นใจได้ว่าความตั้งใจตรงกับการเลือกของผู้ใช้จริงในตัวเลือกของระบบ - ยืนยัน
Activity.getCallingPackage()(การตรวจสอบผู้โทร): เนื่องจากตัวเลือกข้อมูลเข้าสู่ระบบจะเปิดใช้งานกิจกรรมของคุณโดยใช้startActivityForResultกิจกรรมจึงตรวจสอบgetCallingPackage()เพื่อ ยืนยันว่าผู้โทรที่เปิด UI โดยตรงคือเฟรมเวิร์กตัวเลือกข้อมูลเข้าสู่ระบบที่เชื่อถือได้ (ในอุปกรณ์ Android ที่มีบริการ Google Play,"com.google.android.gms") ปฏิเสธคำขอหากแพ็กเกจที่เรียกใช้ไม่รู้จัก - ตรวจสอบ
request.callingAppInfo(ข้อมูลแอปเป้าหมาย):callingAppInfoระบุแอปพลิเคชันปลายทางที่จะ รับข้อมูลเข้าสู่ระบบที่นำเข้า คุณสามารถใช้ข้อมูลนี้ตามที่เห็นสมควรเพื่อความโปร่งใส (เช่น การแสดงชื่อและไอคอนแอปปลายทางใน UI การยืนยัน) หรือเพื่อยืนยันว่าแอปพลิเคชันปลายทางเชื่อถือได้หรือไม่
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หรือการเรียกใช้ในระดับ OS ที่ไม่รองรับ)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)