Holder API ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบช่วยให้แอปผู้ถือข้อมูลเข้าสู่ระบบ Android (หรือที่เรียกว่า "กระเป๋าเงิน") จัดการและแสดงข้อมูลเข้าสู่ระบบดิจิทัลต่อผู้ยืนยันได้
แนวคิดหลัก
คุณควรทำความคุ้นเคยกับแนวคิดต่อไปนี้ก่อน ใช้ Holder API
รูปแบบข้อมูลเข้าสู่ระบบ
สามารถจัดเก็บข้อมูลเข้าสู่ระบบในแอปผู้ถือในรูปแบบข้อมูลเข้าสู่ระบบต่างๆ ได้ รูปแบบเหล่านี้ เป็นข้อกำหนดเกี่ยวกับวิธีแสดงข้อมูลเข้าสู่ระบบ และแต่ละรูปแบบ จะมีข้อมูลต่อไปนี้เกี่ยวกับข้อมูลเข้าสู่ระบบ
- ประเภท: หมวดหมู่ เช่น ปริญญาบัณฑิตหรือใบขับขี่บนอุปกรณ์เคลื่อนที่
- พร็อพเพอร์ตี้: แอตทริบิวต์ เช่น ชื่อและนามสกุล
- การเข้ารหัส: วิธีการจัดโครงสร้างข้อมูลเข้าสู่ระบบ เช่น SD-JWT หรือ mdoc
- ความถูกต้อง: วิธีการตรวจสอบความถูกต้องของข้อมูลเข้าสู่ระบบ ด้วยการเข้ารหัส
รูปแบบข้อมูลเข้าสู่ระบบแต่ละรูปแบบจะเข้ารหัสและตรวจสอบแตกต่างกันเล็กน้อย แต่ในแง่ของการทำงานแล้วจะเหมือนกัน
รีจิสทรีรองรับ 2 รูปแบบต่อไปนี้
- SD-JWT: เป็นไปตามข้อกำหนดของข้อมูลเข้าสู่ระบบที่ตรวจสอบได้ซึ่งอิงตาม IETF SD-JWT (SD-JWT VC)
- เอกสารบนอุปกรณ์เคลื่อนที่หรือ mdoc: เป็นไปตามข้อกำหนด ISO/IEC 18013-5:2021
ผู้ยืนยันอาจส่งคำขอ OpenID4VP สำหรับ SD-JWT และ mdoc เมื่อใช้ เครื่องมือจัดการข้อมูลเข้าสู่ระบบ ตัวเลือกจะแตกต่างกันไปตามกรณีการใช้งานและอุตสาหกรรม ที่เลือก
การลงทะเบียนข้อมูลเมตาของข้อมูลเข้าสู่ระบบ
เครื่องมือจัดการข้อมูลเข้าสู่ระบบไม่ได้จัดเก็บข้อมูลเข้าสู่ระบบของผู้ถือโดยตรง แต่จะจัดเก็บข้อมูลเมตาของข้อมูลเข้าสู่ระบบแทน แอปผู้ถือต้องลงทะเบียนข้อมูลเมตาของข้อมูลเข้าสู่ระบบกับ Credential Manager โดยใช้ RegistryManager ก่อน กระบวนการลงทะเบียนนี้
จะสร้างบันทึกรีจิสทรีซึ่งมีวัตถุประสงค์หลัก 2 ประการ ได้แก่
- การจับคู่: ระบบจะใช้ข้อมูลเมตาของข้อมูลรับรองที่ลงทะเบียนเพื่อจับคู่กับคำขอของเครื่องมือยืนยันในอนาคต
- การแสดงผล: องค์ประกอบ UI ที่กำหนดเองจะแสดงต่อผู้ใช้ในอินเทอร์เฟซตัวเลือกข้อมูลเข้าสู่ระบบ
คุณจะใช้คลาส OpenId4VpRegistry เพื่อลงทะเบียนข้อมูลเข้าสู่ระบบดิจิทัล
เนื่องจากรองรับทั้งรูปแบบข้อมูลเข้าสู่ระบบ mdoc และ SD-JWT ผู้ยืนยันจะส่งคำขอ OpenID4VP เพื่อขอข้อมูลเข้าสู่ระบบเหล่านี้
ลงทะเบียนข้อมูลเข้าสู่ระบบของแอป
หากต้องการใช้ Credential Manager Holder API ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในสคริปต์บิลด์ของโมดูลแอป
Groovy
dependencies { // Use to implement credentials registrys implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04" }
Kotlin
dependencies { // Use to implement credentials registrys implementation("androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-provider:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04") }
สร้าง RegistryManager
สร้างอินสแตนซ์ RegistryManager และลงทะเบียนคำขอ OpenId4VpRegistry กับอินสแตนซ์นั้น
// Create the registry manager
val registryManager = RegistryManager.create(context)
// The guide covers how to build this out later
val registryRequest = OpenId4VpRegistry(credentialEntries, id)
try {
registryManager.registerCredentials(registryRequest)
} catch (e: Exception) {
// Handle exceptions
}
สร้างคำขอ OpenId4VpRegistry
ดังที่กล่าวไว้ก่อนหน้านี้ คุณจะต้องลงทะเบียน OpenId4VpRegistry เพื่อจัดการ
คำขอ OpenID4VP จากผู้ยืนยัน เราจะถือว่าคุณมีข้อมูลในเครื่องบางประเภทที่โหลดด้วยข้อมูลเข้าสู่ระบบของกระเป๋าเงิน (เช่น sdJwtsFromStorage)
ตอนนี้คุณจะแปลงข้อมูลเหล่านั้นเป็นข้อมูลที่เทียบเท่ากับ Jetpack DigitalCredentialEntry ของเรา
โดยอิงตามรูปแบบของข้อมูล ซึ่งก็คือ SdJwtEntry หรือ MdocEntry สำหรับ SD-JWT หรือ mdoc
ตามลำดับ
เพิ่ม 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
}
เพิ่ม mdocs ลงในรีจิสทรี
แมปข้อมูลเข้าสู่ระบบ 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.
)
ตอนนี้เราพร้อมที่จะลงทะเบียนข้อมูลเข้าสู่ระบบของคุณกับ CredentialManager แล้ว
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
ตอนนี้คุณได้ลงทะเบียนข้อมูลเข้าสู่ระบบกับเครื่องมือจัดการข้อมูลเข้าสู่ระบบแล้ว
การจัดการข้อมูลเมตาของแอป
ข้อมูลเมตาที่แอปผู้ถือบัตรลงทะเบียนกับ CredentialManager มีพร็อพเพอร์ตี้ต่อไปนี้
- ความคงทน: ระบบจะบันทึกข้อมูลไว้ในเครื่องและจะยังคงอยู่แม้จะรีบูตเครื่อง
- ที่เก็บข้อมูลแบบแยกส่วน: ระบบจะจัดเก็บบันทึกรีจิสทรีของแต่ละแอปแยกกัน ซึ่งหมายความว่าแอปหนึ่งจะเปลี่ยนแปลงบันทึกรีจิสทรีของอีกแอปหนึ่งไม่ได้
- การอัปเดตที่ใช้คีย์: บันทึกรีจิสทรีของแต่ละแอปจะใช้คีย์
idซึ่งช่วยให้ระบุ อัปเดต หรือลบบันทึกได้อีกครั้ง - การอัปเดตข้อมูลเมตา: แนวทางปฏิบัติที่ดีคือการอัปเดตข้อมูลเมตาที่คงอยู่
ทุกครั้งที่แอปมีการเปลี่ยนแปลงหรือโหลดเป็นครั้งแรก หากมีการเรียกรีจิสทรีหลายครั้งภายใต้
idเดียวกัน การเรียกครั้งล่าสุดจะเขียนทับระเบียนก่อนหน้าทั้งหมด หากต้องการ อัปเดต ให้ลงทะเบียนอีกครั้งโดยไม่ต้องล้างระเบียนเก่าก่อน
ไม่บังคับ: สร้างตัวจับคู่
Matcher คือไบนารี Wasm ที่ตัวจัดการข้อมูลเข้าสู่ระบบเรียกใช้ในแซนด์บ็อกซ์เพื่อกรอง ข้อมูลเข้าสู่ระบบที่ลงทะเบียนไว้กับคำขอ Verifier ที่เข้ามา
- ตัวเทียบเริ่มต้น: คลาส
OpenId4VpRegistryจะรวม ตัวเทียบOpenId4VPเริ่มต้น (OpenId4VpDefaults.DEFAULT_MATCHER) โดยอัตโนมัติเมื่อคุณ สร้างอินสแตนซ์ สำหรับกรณีการใช้งาน OpenID4VP มาตรฐานทั้งหมด ไลบรารีจะจัดการ การจับคู่ให้คุณ - เครื่องมือจับคู่ที่กำหนดเอง: คุณจะติดตั้งใช้งานเครื่องมือจับคู่ที่กำหนดเองก็ต่อเมื่อคุณ รองรับโปรโตคอลที่ไม่เป็นมาตรฐานซึ่งต้องใช้ตรรกะการจับคู่ของตัวเอง
จัดการข้อมูลเข้าสู่ระบบที่เลือก
เมื่อผู้ใช้เลือกข้อมูลเข้าสู่ระบบ แอปผู้ถือจะต้องจัดการคำขอ
คุณจะต้องกำหนดกิจกรรมที่รับฟังตัวกรอง 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) ไปยัง
CallingAppInfoAPI ของgetOrigin()
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
หากต้นทางไม่ว่างเปล่า: ระบบจะแสดงผลต้นทางหาก packageName และ
ลายนิ้วมือของใบรับรองที่ได้จาก signingInfo ตรงกับลายนิ้วมือของแอปที่พบ
ในรายการที่อนุญาตซึ่งส่งไปยัง getOrigin() API หลังจากได้รับค่าต้นทางแล้ว แอปผู้ให้บริการควรพิจารณาว่าการเรียกนี้เป็นการเรียกที่มีสิทธิ์และตั้งค่าต้นทางนี้ในการตอบกลับ OpenID4VP แทนที่จะคำนวณต้นทางโดยใช้ลายเซ็นของแอปที่เรียก
เครื่องมือจัดการรหัสผ่านบน Google ใช้รายการที่อนุญาตที่เปิดให้ใช้งานทั่วไปสำหรับการเรียกไปยัง
getOrigin() ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถใช้รายการนี้หรือระบุรายการของคุณเองในรูปแบบ JSON ตามที่ API อธิบายไว้ ผู้ให้บริการจะเป็นผู้เลือก
รายการที่จะใช้ หากต้องการรับสิทธิ์เข้าถึงระดับสูงด้วยผู้ให้บริการข้อมูลเข้าสู่ระบบของบุคคลที่สาม
โปรดอ่านเอกสารประกอบที่บุคคลที่สามจัดทำ
หากต้นทางว่างเปล่า คำขอของเครื่องมือยืนยันมาจากแอป Android แอป
ต้นทางที่จะใส่ในการตอบกลับ OpenID4VP ควรคำนวณเป็น
android:apk-key-hash:<encoded SHA 256 fingerprint>
val appSigningInfo = request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory[0]?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash = Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
return "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()
ดูตัวอย่างการส่งคืนการตอบกลับของข้อมูลเข้าสู่ระบบในบริบททั้งหมดได้ในแอปตัวอย่าง