本指南說明如何在採用 WebView 的 Android 應用程式中,使用 Credential Manager 要求數位憑證。使用 JavaScript Bridge 促進網頁和應用程式之間的通訊。
總覽
以下步驟說明 WebView 數位憑證要求的生命週期:
- 要求產生:WebView 中的網頁向應用程式伺服器提出要求,伺服器會產生 OpenID4VP 要求 JSON 並傳回。
- 要求啟動:網頁會使用
navigator.credentials.get({ digital: ...}),透過要求 JSON 要求數位憑證。 - 攔截:插入的
bridge.js會攔截這項呼叫、擷取特定 OpenID4VP 要求 JSON,並傳遞至 Android 應用程式。 - Android 處理:Android 應用程式收到訊息,並呼叫
CredentialManager.getCredentialAPI。 - 使用者選取:Credential Manager 會顯示 UI,提示使用者分享憑證。憑證會傳回 Android 應用程式。
- 橋接回應:Android 應用程式使用橋接器將回應傳回網頁。
- 完成:網頁接收並處理回應。
必要條件
在 WebView 中導入數位憑證的 JavaScript 橋接之前,請確認專案符合下列規定:
- Android 版本相容性:應用程式必須指定 Android 6.0 (API 級別 23) 以上版本。
- 主機應用程式控制權:您必須控制託管
WebView的 Android 應用程式,才能在載入網頁前附加WebMessageListener。 - HTTPS 和頂層框架:要求憑證的網頁必須透過
https://提供,並在主框架中執行 (isMainFrame必須是true)。不支援子框架和嵌入的<iframe>元素。 - 實驗性 API 選擇加入:由於 Digital Credentials API 屬於實驗性質,因此您必須使用
@OptIn(ExperimentalDigitalCredentialApi::class)註解使用GetDigitalCredentialOption的類別或方法。
新增依附元件
將 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 中導入數位憑證
您必須在 WebView 中加入 JavaScript,才能提出憑證要求,並使用 bridge.js 攔截要求。
建立 JavaScript 要求
如要在 WebView 中執行的 JavaScript 和 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.js 的檔案,或使用 evaluateJavascript 插入這段程式碼。這個指令碼會覆寫 navigator.credentials.get,攔截要求並將內部的 OpenID4VP JSON 字串傳送至 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 傳送的憑證要求。
建立 Android 事件監聽器
設定實作 WebViewCompat.WebMessageListener 的 DigitalCredentialWebListener 類別。這個類別會處理 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"
}
}
將架構套用至活動
在 Activity 或 View 中,註冊 WebMessageListener 和相關聯的函式。
// 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。