ผสานรวมแอปผู้ถือบัตรกับเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

Holder API ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบช่วยให้แอปผู้ถือข้อมูลเข้าสู่ระบบ Android (หรือที่เรียกว่า "กระเป๋าเงิน") จัดการและแสดงข้อมูลเข้าสู่ระบบดิจิทัลต่อผู้ยืนยันได้

รูปภาพแสดง UI ของข้อมูลเข้าสู่ระบบดิจิทัลในเครื่องมือจัดการข้อมูลเข้าสู่ระบบ
รูปที่ 1 UI ตัวเลือกข้อมูลเข้าสู่ระบบดิจิทัล

แนวคิดหลัก

คุณควรทำความคุ้นเคยกับแนวคิดต่อไปนี้ก่อน ใช้ Holder API

รูปแบบข้อมูลเข้าสู่ระบบ

สามารถจัดเก็บข้อมูลเข้าสู่ระบบในแอปผู้ถือในรูปแบบข้อมูลเข้าสู่ระบบต่างๆ ได้ รูปแบบเหล่านี้ เป็นข้อกำหนดเกี่ยวกับวิธีแสดงข้อมูลเข้าสู่ระบบ และแต่ละรูปแบบ จะมีข้อมูลต่อไปนี้เกี่ยวกับข้อมูลเข้าสู่ระบบ

  • ประเภท: หมวดหมู่ เช่น ปริญญาบัณฑิตหรือใบขับขี่บนอุปกรณ์เคลื่อนที่
  • พร็อพเพอร์ตี้: แอตทริบิวต์ เช่น ชื่อและนามสกุล
  • การเข้ารหัส: วิธีการจัดโครงสร้างข้อมูลเข้าสู่ระบบ เช่น SD-JWT หรือ mdoc
  • ความถูกต้อง: วิธีการตรวจสอบความถูกต้องของข้อมูลเข้าสู่ระบบ ด้วยการเข้ารหัส

รูปแบบข้อมูลเข้าสู่ระบบแต่ละรูปแบบจะเข้ารหัสและตรวจสอบแตกต่างกันเล็กน้อย แต่ในแง่ของการทำงานแล้วจะเหมือนกัน

รีจิสทรีรองรับ 2 รูปแบบต่อไปนี้

ผู้ยืนยันอาจส่งคำขอ 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)
    }
}

ตัวอย่างของเนื้อหาดังกล่าวจะอยู่ในกระเป๋าเงินตัวอย่าง

ตรวจสอบข้อมูลระบุตัวตนของผู้ยืนยัน

  1. ดึง ProviderGetCredentialRequest จาก Intent:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
  1. ตรวจสอบแหล่งที่มาที่มีสิทธิ์: แอปที่มีสิทธิ์ (เช่น เว็บเบราว์เซอร์) สามารถ โทรในนามของผู้ยืนยันรายอื่นได้โดยการตั้งค่าพารามิเตอร์ต้นทาง หากต้องการดึงข้อมูลต้นทางนี้ คุณต้องส่งรายการผู้โทรที่มีสิทธิ์และเชื่อถือได้ (รายการที่อนุญาตในรูปแบบ JSON) ไปยัง CallingAppInfo API ของ 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()

ดูตัวอย่างการส่งคืนการตอบกลับของข้อมูลเข้าสู่ระบบในบริบททั้งหมดได้ในแอปตัวอย่าง