คู่มือนี้อธิบายวิธีขอข้อมูลเข้าสู่ระบบดิจิทัลโดยใช้ Credential Manager ในแอป Android ที่ใช้ WebView ใช้ JavaScript Bridge เพื่ออำนวยความสะดวกในการสื่อสารระหว่างหน้าเว็บกับแอป
ภาพรวม
ขั้นตอนต่อไปนี้อธิบายวงจรของคำขอข้อมูลเข้าสู่ระบบดิจิทัลจาก WebView
- การสร้างคำขอ: หน้าเว็บใน WebView จะส่งคำขอไปยังเซิร์ฟเวอร์แอป ซึ่งจะสร้าง JSON คำขอ OpenID4VP และส่งกลับมา
- การเริ่มต้นคำขอ: หน้าเว็บใช้
navigator.credentials.get({ digital: ...}) เพื่อขอข้อมูลเข้าสู่ระบบดิจิทัลโดยใช้ JSON ของคำขอ - การสกัดกั้น:
bridge.jsที่แทรกจะสกัดกั้นการเรียกนี้ ดึงข้อมูล JSON ของคำขอ OpenID4VP ที่เฉพาะเจาะจง และส่งไปยังแอป Android - การจัดการของ Android: แอป Android จะได้รับข้อความและเรียกใช้ API
CredentialManager.getCredential - การเลือกของผู้ใช้: Credential Manager จะแสดง UI ที่แจ้งให้ผู้ใช้แชร์ข้อมูลเข้าสู่ระบบ ระบบจะส่งข้อมูลเข้าสู่ระบบกลับไปยังแอป Android
- การตอบกลับของบริดจ์: แอป Android จะส่งการตอบกลับกลับไปยังหน้าเว็บโดยใช้บริดจ์
- เสร็จสมบูรณ์: หน้าเว็บได้รับและจัดการการตอบกลับ
สิ่งที่ต้องมีก่อน
ก่อนติดตั้งใช้งาน JavaScript Bridge สำหรับข้อมูลเข้าสู่ระบบดิจิทัลใน WebView โปรดตรวจสอบว่าโปรเจ็กต์ของคุณเป็นไปตามข้อกำหนดต่อไปนี้
- ความเข้ากันได้ของเวอร์ชัน Android: แอปของคุณต้องกำหนดเป้าหมายเป็น Android 6.0 (API ระดับ 23) ขึ้นไป
- การควบคุมแอปโฮสต์: คุณต้องควบคุมแอป Android ที่โฮสต์
WebViewเพื่อให้แนบWebMessageListenerก่อนโหลดหน้าเว็บได้ - HTTPS และเฟรมระดับบนสุด: หน้าเว็บที่ขอข้อมูลเข้าสู่ระบบต้องแสดงผ่าน
https://และทำงานในเฟรมหลัก (isMainFrameต้องเป็นtrue) ระบบไม่รองรับเฟรมย่อยและองค์ประกอบ<iframe>ที่ฝังอยู่ - การเลือกใช้ API เวอร์ชันทดลอง: เนื่องจาก Digital Credentials API เป็นเวอร์ชันทดลอง คุณจึงต้องใส่คำอธิบายประกอบคลาสหรือเมธอดที่ใช้
GetDigitalCredentialOptionด้วย@OptIn(ExperimentalDigitalCredentialApi::class)
เพิ่มทรัพยากร Dependency
เพิ่มทรัพยากร Dependency ของ Credential Manager และ AndroidX WebKit ลงในไฟล์ build.gradle.kts ของโมดูลแอป
dependencies {
// Credential Manager dependencies
implementation("androidx.credentials:credentials:1.7.0-alpha03")
implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
// AndroidX WebKit (required for WebMessageListener bridge)
implementation("androidx.webkit:webkit:1.14.0")
}
ใช้ข้อมูลเข้าสู่ระบบดิจิทัลใน WebView
คุณจะต้องเพิ่ม JavaScript ลงใน WebView เพื่อส่งคำขอข้อมูลเข้าสู่ระบบและสกัดกั้นด้วย bridge.js
สร้างคำขอ JavaScript
หากต้องการสื่อสารระหว่าง JavaScript ที่ทำงานใน WebView กับโค้ด Android ให้แทรกโค้ด JavaScript ลงใน WebView ดังนี้
// Example digital credential request to request verified email
const request = {
"providers": [
{
"protocol": "openid4vp",
"request": JSON.stringify({
"requests": [
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "n-0S6_W_2F", // Generate dynamically
"dcql_query": {
"credentials": [
{
"id": "user_info_query",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["UserInfoCredential"]
},
"claims": [
{"path": ["email"]},
{"path": ["email_verified"]},
{"path": ["name"]}
]
}
]
}
}
}
]
})
}
]
};
try {
const credential = await navigator.credentials.get({
digital: request
});
// Process response
} catch (err) {
// Handle error
}
สร้าง JavaScript Bridge
สร้างไฟล์ชื่อ bridge.js หรือแทรกโค้ดนี้โดยใช้ evaluateJavascript
สคริปต์นี้จะลบล้าง navigator.credentials.get เพื่อสกัดกั้นคำขอและส่งสตริง JSON ของ OpenID4VP ภายในไปยังแอป Android
(function() {
if (!navigator.credentials) {
navigator.credentials = {};
}
const originalGet = navigator.credentials.get;
// Overrides navigator.credentials.get() to intercept W3C Digital Credential requests.
navigator.credentials.get = async function(options) {
if (options && options.digital) {
if (window.DigitalCredentialBridge) {
return new Promise((resolve, reject) => {
const digital = options.digital;
if (digital.providers && digital.providers.length > 0) {
const provider = digital.providers[0];
if (provider.protocol === "openid4vp") {
const requestJson = provider.request;
const message = {
type: "get",
request: requestJson
};
console.log("Sending message to native bridge:", JSON.stringify(message));
// Send the request string to native Android code
window.DigitalCredentialBridge.postMessage(JSON.stringify(message));
// Receive the response sent from native code using replyProxy.postMessage()
window.DigitalCredentialBridge.onmessage = function(event) {
console.log("Received response from native bridge:", event.data);
try {
const response = JSON.parse(event.data);
if (response.error) {
reject(new Error(response.error));
} else {
resolve(response);
}
} catch (e) {
// If it's not JSON, just resolve with the raw data
resolve(event.data);
}
};
} else {
reject(new Error("Unsupported protocol: " + provider.protocol));
}
} else {
reject(new Error("No providers specified in digital credential request"));
}
});
} else {
console.warn("DigitalCredentialBridge not found, falling back to original");
}
}
if (originalGet) {
return originalGet.call(navigator.credentials, options);
} else {
throw new Error("navigator.credentials.get is not supported and no bridge available");
}
};
})();
ใช้เอกสารรับรองดิจิทัลใน Android
กำหนดค่าแอป Android ให้จัดการคำขอข้อมูลเข้าสู่ระบบที่ส่งจาก bridge.js
สร้าง Listener ของ Android
ตั้งค่าคลาส DigitalCredentialWebListener ที่ใช้ WebViewCompat.WebMessageListener คลาสนี้จะจัดการข้อความแบบ 2 ทิศทางระหว่าง JavaScript กับโค้ด Android แบบเนทีฟ โดยบังคับใช้การตรวจสอบความปลอดภัยใน isMainFrame และ sourceOrigin รวมถึงป้องกันคำขอที่ทับซ้อนกัน
ใช้เครื่องจัดการ Credential Manager
เรียกใช้ getCredential เพื่อขอข้อมูลเข้าสู่ระบบดิจิทัลจากอุปกรณ์
@OptIn(ExperimentalDigitalCredentialApi::class)
class CredentialManagerHandler(private val context: Context) {
private val credentialManager = CredentialManager.create(context)
suspend fun getDigitalCredential(requestJson: String): String {
val digitalCredentialOption = GetDigitalCredentialOption(requestJson)
val getCredRequest = GetCredentialRequest(listOf(digitalCredentialOption))
return try {
val result = credentialManager.getCredential(context, getCredRequest)
val credential = result.credential
if (credential is DigitalCredential) {
credential.credentialJson
} else {
"{\"error\": \"Unexpected credential type\"}"
}
} catch (e: GetCredentialException) {
"{\"error\": \"${e.message}\"}"
} catch (e: Exception) {
"{\"error\": \"Unknown exception\"}"
}
}
companion object {
private const val TAG = "CredManHandler"
}
}
ใช้เฟรมเวิร์กกับกิจกรรมของคุณ
ลงทะเบียน WebMessageListener และฟังก์ชันที่เกี่ยวข้องใน Activity หรือ View
// Inside your Activity or View setup
AndroidView(
factory = { context ->
val handler = CredentialManagerHandler(context)
WebView(context).apply {
settings.javaScriptEnabled = true
// Register the bridge before loading URL
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
val listener = DigitalCredentialWebListener(coroutineScope, credentialManagerHandler)
WebViewCompat.addWebMessageListener(
webView,
INTERFACE_NAME,
setOf("*"),
listener
)
loadUrl("https://your-secure-domain.com/login")
}
},
)
กำลังทดสอบโฟลว์
ทดสอบขั้นตอนเพื่อยืนยันการสื่อสารระหว่าง WebView กับแอป Android ตัวเลือกข้อมูลเข้าสู่ระบบจะปรากฏใน WebView ระหว่างกระบวนการ และข้อมูลเข้าสู่ระบบที่เลือกจะกลับไปยัง WebView เมื่อดำเนินการเสร็จสมบูรณ์