Credential Manager Holder API ช่วยให้แอปผู้ถือ (หรือที่เรียกว่า "Wallet") ของ Android จัดการและแสดงข้อมูลเข้าสู่ระบบดิจิทัลต่อผู้ตรวจสอบได้ คู่มือนี้อธิบายวิธีลงทะเบียนข้อมูลเข้าสู่ระบบของแอปผู้ถือด้วย Credential Manager และวิธีจัดการคำขอการนำเสนอเมื่อผู้ใช้เลือกข้อมูลเข้าสู่ระบบ Holder API รองรับใน Android 6 (ระดับ API 23) ขึ้นไป
เพิ่มทรัพยากร Dependency
หากต้องการใช้ Credential Manager Holder API ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในสคริปต์ build.gradle ของโมดูลแอป
dependencies {
// Used to implement credential registries
implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-digitalcredentials-openid:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-digitalcredentials-sdjwtvc:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha05"
}
ลงทะเบียนข้อมูลเข้าสู่ระบบ
ก่อนอื่น ให้สร้างอินสแตนซ์ RegistryManager ที่คุณจะใช้เพื่อลงทะเบียนคำขอ OpenId4VpRegistry
// Create the registry manager
val registryManager = RegistryManager.create(context)
สร้างคำขอ OpenId4VpRegistry
คุณต้องลงทะเบียน OpenId4VpRegistry เพื่อจัดการคำขอ OpenID4VP จากผู้ตรวจสอบ สมมติว่าคุณมีประเภทข้อมูลในเครื่องที่โหลดด้วยข้อมูลเข้าสู่ระบบ Wallet (เช่น sdJwtsFromStorage และ mdocsFromStorage) คุณต้องแปลงประเภทข้อมูลดังกล่าวเป็นประเภทข้อมูล Jetpack DigitalCredentialEntry ที่เทียบเท่ากัน ซึ่งก็คือ SdJwtEntry และ MdocEntry
เพิ่ม SD-JWT ลงในรีจิสทรี
แมปข้อมูลเข้าสู่ระบบ SD-JWT ในเครื่องแต่ละรายการกับ SdJwtEntry สำหรับรีจิสทรี ดังนี้
fun mapToSdJwtEntries(sdJwtsFromStorage: List<StoredSdJwtEntry>): List<SdJwtEntry> {
val list = mutableListOf<SdJwtEntry>()
for (sdJwt in sdJwtsFromStorage) {
list.add(
SdJwtEntry(
verifiableCredentialType = sdJwt.getVCT(),
claims = sdJwt.getClaimsList(),
entryDisplayPropertySet = sdJwt.toDisplayProperties(),
id = sdJwt.getId() // Make sure this cannot be readily guessed
)
)
}
return list
}
เพิ่ม mdoc ลงใน Registry
แมปข้อมูลเข้าสู่ระบบ mdoc ในเครื่องกับประเภท Jetpack MdocEntry:
fun mapToMdocEntries(mdocsFromStorage: List<StoredMdocEntry>): List<MdocEntry> {
val list = mutableListOf<MdocEntry>()
for (mdoc in mdocsFromStorage) {
list.add(
MdocEntry(
docType = mdoc.retrieveDocType(),
fields = mdoc.getFields(),
entryDisplayPropertySet = mdoc.toDisplayProperties(),
id = mdoc.getId() // Make sure this cannot be readily guessed
)
)
}
return list
}
ประเด็นสำคัญเกี่ยวกับโค้ด
- รหัสที่เข้ารหัส: วิธีหนึ่งในการกำหนดค่าช่อง
IDคือการลงทะเบียนตัวระบุข้อมูลเข้าสู่ระบบที่เข้ารหัส เพื่อให้มีเพียงคุณเท่านั้นที่ถอดรหัสค่าได้ - การแปลภาษา: ช่องแสดงผล UI สำหรับทั้ง 2 รูปแบบควรได้รับการแปลภาษา
รวมและลงทะเบียนคำขอ
รวมรายการที่แปลงแล้วและลงทะเบียนคำขอด้วย RegistryManager:
val credentialEntries =
mapToSdJwtEntries(sdJwtsFromStorage) + mapToMdocEntries(mdocsFromStorage)
val openidRegistryRequest = OpenId4VpRegistry(
credentialEntries = credentialEntries,
id = "my-wallet-openid-registry-v1" // A stable, unique ID to identify your registry record.
)
ไม่บังคับ: การออกบัตรในหน้าเว็บ
หากไม่มีข้อมูลเข้าสู่ระบบประเภทที่ขอสำหรับผู้ใช้ คุณสามารถแสดงตัวเลือกเพื่อเพิ่มข้อมูลเข้าสู่ระบบลงในที่เก็บข้อมูลได้ โดยเพิ่มรายการ inlineIssuanceEntries ที่มีข้อมูลเข้าสู่ระบบที่รองรับลงใน OpenId4VpRegistry ดังนี้
val openidRegistryRequest = OpenId4VpRegistry(
credentialEntries = credentialEntries,
inlineIssuanceEntries = emptyList(),
listOf(
MdocInlineIssuanceEntry(
id = "Issuance",
display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
subtitle = "Mobile Drivers License, State Id, and Others",
),
supportedMdocs = setOf(
MdocInlineIssuanceEntry.SupportedMdoc(
"eu.europa.ec.eudi.pid.1"
),
MdocInlineIssuanceEntry.SupportedMdoc(
"org.iso.18013.5.1.mDL1"
),
)
),
SdJwtInlineIssuanceEntry(
id = "sd-jwt-issuance",
display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
subtitle = "Mobile Drivers License, State Id, and Others",
),
supportedSdJwts = setOf(
SdJwtInlineIssuanceEntry.SupportedSdJwt("urn:openid:interop:id:1")
)
)
),
id = "my-wallet-openid-registry-v1"
)
ลงทะเบียนข้อมูลเข้าสู่ระบบด้วย CredentialManager
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
การจัดการข้อมูลเมตาของแอป
ข้อมูลเมตาที่แอปผู้ถือลงทะเบียนกับ CredentialManager มีพร็อพเพอร์ตี้ต่อไปนี้
- ความต่อเนื่อง: ระบบจะบันทึกข้อมูลไว้ในเครื่องและคงอยู่แม้จะรีบูต
- พื้นที่เก็บข้อมูลแบบแยกส่วน: ระบบจะจัดเก็บระเบียนรีจิสทรีของแต่ละแอปแยกกัน ซึ่งหมายความว่าแอปหนึ่งจะไม่สามารถเปลี่ยนระเบียนรีจิสทรีของแอปอื่นได้
- การอัปเดตที่มีคีย์: ระเบียนรีจิสทรีของแต่ละแอปจะมีคีย์เป็น
idซึ่งช่วยให้ระบุ อัปเดต หรือลบระเบียนซ้ำได้ - การอัปเดตข้อมูลเมตา: แนวทางปฏิบัติแนะนำคือการอัปเดตข้อมูลเมตาที่เก็บไว้เมื่อใดก็ตามที่แอปมีการเปลี่ยนแปลงหรือโหลดเป็นครั้งแรก หากมีการเรียกใช้รีจิสทรีหลายครั้งภายใต้
idเดียวกัน การเรียกใช้ครั้งล่าสุดจะเขียนทับระเบียนก่อนหน้าทั้งหมด หากต้องการอัปเดต ให้ลงทะเบียนอีกครั้งโดยไม่จำเป็นต้องล้างบันทึกเก่าก่อน
ไม่บังคับ: สร้างตัวจับคู่
Matcher คือไบนารี Wasm ที่ Credential Manager เรียกใช้ในแซนด์บ็อกซ์เพื่อกรองข้อมูลเข้าสู่ระบบที่ลงทะเบียนไว้กับคำขอ Verifier ขาเข้า
- ตัวจับคู่เริ่มต้น: คลาส
OpenId4VpRegistryจะรวมตัวจับคู่OpenId4VPเริ่มต้นโดยอัตโนมัติ (OpenId4VpDefaults.DEFAULT_MATCHER) เมื่อคุณสร้างอินสแตนซ์ สำหรับกรณีการใช้งาน OpenID4VP มาตรฐานทั้งหมด ไลบรารีจะจัดการการจับคู่ให้คุณ - Custom matcher: คุณจะใช้ Custom matcher ก็ต่อเมื่อคุณรองรับโปรโตคอลที่ไม่ได้มาตรฐานซึ่งต้องใช้ตรรกะการจับคู่ของตัวเอง
จัดการข้อมูลเข้าสู่ระบบที่เลือก
เมื่อผู้ใช้เลือกข้อมูลเข้าสู่ระบบ แอปที่เก็บข้อมูลจะต้องจัดการคำขอ
คุณจะต้องกำหนดกิจกรรมที่คอยฟังตัวกรอง Intent androidx.credentials.registry.provider.action.GET_CREDENTIAL
วอลเล็ตตัวอย่างของเราแสดงให้เห็นถึงขั้นตอนนี้
Intent จะเปิดกิจกรรมของคุณด้วยคำขอ Verifier และต้นทางการโทร ซึ่งคุณดึงข้อมูลด้วย
PendingIntentHandler.retrieveProviderGetCredentialRequest
ฟังก์ชัน ซึ่งจะแสดงผล ProviderGetCredentialRequest ที่มีข้อมูลทั้งหมดที่เชื่อมโยงกับคำขอของผู้ตรวจสอบ ซึ่งมีองค์ประกอบหลัก 3 อย่าง
- แอปการโทร: แอปที่ส่งคำขอ ซึ่งเรียกข้อมูลได้ด้วย
getCallingAppInfoคุณสามารถใช้ข้อมูลนี้เพื่อตรวจสอบตัวตนของผู้ยืนยันได้ - ข้อมูลเข้าสู่ระบบที่เลือก: ข้อมูลเกี่ยวกับตัวเลือกที่ผู้ใช้เลือก ซึ่งดึงมาผ่าน
selectedCredentialSet extension methodโดยข้อมูลนี้จะตรงกับรหัสข้อมูลเข้าสู่ระบบที่คุณลงทะเบียนไว้ - คำขอที่เฉพาะเจาะจง: คำขอที่เฉพาะเจาะจงซึ่งส่งโดยผู้ตรวจสอบ ซึ่งดึงมาจากเมธอด
getCredentialOptionsสำหรับขั้นตอนคำขอข้อมูลเข้าสู่ระบบดิจิทัล คุณจะเห็นGetDigitalCredentialOptionรายการเดียวในรายการนี้
คุณประมวลผลคำขอการนำเสนอได้ด้วยโค้ดต่อไปนี้
request.credentialOptions.forEach { option ->
if (option is GetDigitalCredentialOption) {
Log.i(TAG, "Got DC request: ${option.requestJson}")
processRequest(option.requestJson)
}
}
ดูตัวอย่างได้ในวอลเล็ตตัวอย่าง
ตรวจสอบข้อมูลระบุตัวตนของผู้ตรวจสอบ
ก่อนอื่น ให้ดึง ProviderGetCredentialRequest ออกจาก Intent ดังนี้
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
ตรวจสอบแอปที่มีสิทธิ์: แอปที่มีสิทธิ์ (เช่น เว็บเบราว์เซอร์) สามารถเรียกใช้ในนามของผู้ตรวจสอบสิทธิ์รายอื่นๆ ได้โดยการตั้งค่าพารามิเตอร์ต้นทาง หากต้องการดึงข้อมูลต้นทางนี้ ให้ส่งรายการผู้โทรที่ได้รับสิทธิ์และเชื่อถือได้ (รายการที่อนุญาตในรูปแบบ JSON) ไปยัง CallingAppInfo getOrigin API
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
หาก origin ไม่ว่างเปล่า: ระบบจะแสดงผลต้นทางหาก packageName และลายนิ้วมือของใบรับรองตรงกับแอปในรายการที่อนุญาต ให้ถือว่าการเรียกนี้เป็นแบบมีสิทธิ์และตั้งค่าต้นทางนี้ในการตอบกลับ OpenID4VP แทนการคำนวณต้นทางโดยใช้ลายเซ็นของแอปที่เรียก
หาก origin ว่างเปล่า: คำขอของผู้ตรวจสอบมาจากแอป Android ต้นทางที่แมปกับการตอบกลับ OpenID4VP ควรคำนวณเป็น android:apk-key-hash:<encoded SHA 256 fingerprint>
val appSigningInfo =
request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.get(0)?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"
แสดงภาพ UI ของที่ยึด
เมื่อเลือกข้อมูลเข้าสู่ระบบ ระบบจะเรียกใช้แอปผู้ถือเพื่อแนะนำผู้ใช้ผ่าน UI ของแอป การจัดการเวิร์กโฟลว์นี้ทำได้ 2 วิธีมาตรฐาน ดังนี้
- หากจำเป็นต้องมีการตรวจสอบสิทธิ์ผู้ใช้เพิ่มเติมเพื่อปล่อยข้อมูลเข้าสู่ระบบ ให้ใช้ BiometricPrompt API ซึ่งแสดงให้เห็นในตัวอย่าง
- หรือกระเป๋าเงินหลายใบอาจเลือกที่จะส่งคืนแบบเงียบๆ โดยแสดงผลกิจกรรมที่ว่างเปล่าซึ่งจะส่งข้อมูลกลับไปยังแอปที่เรียกใช้ทันที วิธีนี้จะช่วยลดการคลิกของผู้ใช้และมอบประสบการณ์การใช้งานที่ราบรื่นยิ่งขึ้น
แสดงการตอบกลับข้อมูลเข้าสู่ระบบ
เมื่อแอปที่เก็บข้อมูลพร้อมที่จะส่งผลลัพธ์กลับแล้ว ให้สิ้นสุดกิจกรรมด้วยการตอบกลับข้อมูลเข้าสู่ระบบดังนี้
PendingIntentHandler.setGetCredentialResponse(
resultData,
GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()
หากมีข้อยกเว้น คุณสามารถส่งข้อยกเว้นของข้อมูลเข้าสู่ระบบในลักษณะเดียวกันได้ ดังนี้
PendingIntentHandler.setGetCredentialException(
resultData,
GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()
ดูตัวอย่างทั้งหมดของการแสดงผลการตอบกลับข้อมูลเข้าสู่ระบบในบริบทได้ที่แอปตัวอย่าง