Yêu cầu thông tin xác thực kỹ thuật số trong WebView

Hướng dẫn này mô tả cách yêu cầu thông tin xác thực kỹ thuật số bằng Trình quản lý thông tin xác thực trong một ứng dụng Android sử dụng WebView. Sử dụng JavaScript Bridge để hỗ trợ hoạt động giao tiếp giữa trang web và ứng dụng.

Tổng quan

Các bước sau đây mô tả vòng đời của một yêu cầu thông tin xác thực kỹ thuật số từ một WebView:

  1. Tạo yêu cầu: Trang web trong WebView gửi yêu cầu đến máy chủ ứng dụng. Máy chủ này sẽ tạo một yêu cầu OpenID4VP JSON rồi gửi lại.
  2. Bắt đầu yêu cầu: Trang web sử dụng navigator.credentials.get({ digital: ... }) để yêu cầu thông tin xác thực kỹ thuật số bằng tệp JSON của yêu cầu.
  3. Chặn: bridge.js được chèn sẽ chặn lệnh gọi này, trích xuất JSON yêu cầu OpenID4VP cụ thể và chuyển JSON đó đến ứng dụng Android.
  4. Xử lý trên Android: Ứng dụng Android nhận thông báo và gọi API CredentialManager.getCredential.
  5. Lựa chọn của người dùng: Trình quản lý thông tin xác thực hiển thị một giao diện người dùng nhắc người dùng chia sẻ thông tin xác thực của họ. Thông tin xác thực được trả về ứng dụng Android.
  6. Phản hồi cầu nối: Ứng dụng Android gửi phản hồi trở lại trang web bằng cầu nối.
  7. Hoàn tất: Trang web nhận và xử lý phản hồi.

Điều kiện tiên quyết

Trước khi triển khai cầu nối JavaScript cho thông tin xác thực kỹ thuật số trong WebView, hãy đảm bảo rằng dự án của bạn đáp ứng các yêu cầu sau:

  • Khả năng tương thích với phiên bản Android: Ứng dụng của bạn phải nhắm đến Android 6.0 (cấp độ API 23) trở lên.
  • Kiểm soát ứng dụng lưu trữ: Bạn phải kiểm soát ứng dụng Android lưu trữ WebView để có thể đính kèm WebMessageListener trước khi tải trang web.
  • HTTPS và khung cấp cao nhất: Trang web yêu cầu thông tin xác thực phải được phân phát qua https:// và chạy trong khung chính (isMainFrame phải là true). Các khung phụ và phần tử <iframe> được nhúng không được hỗ trợ.
  • Chọn sử dụng API thử nghiệm: Vì Digital Credentials API là API thử nghiệm, nên bạn phải chú thích các lớp hoặc phương thức sử dụng GetDigitalCredentialOption bằng @OptIn(ExperimentalDigitalCredentialApi::class).

Thêm phần phụ thuộc

Thêm các phần phụ thuộc Trình quản lý thông tin xác thực và AndroidX WebKit vào tệp build.gradle.kts của mô-đun ứng dụng:

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")
}

Triển khai thông tin xác thực kỹ thuật số trong WebView

Bạn cần thêm JavaScript vào WebView để tạo yêu cầu thông tin xác thực và chặn yêu cầu đó bằng bridge.js.

Tạo yêu cầu JavaScript

Để giao tiếp giữa JavaScript chạy trong WebView và mã Android, hãy chèn mã JavaScript vào 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
}

Tạo cầu nối JavaScript

Tạo một tệp có tên bridge.js hoặc chèn mã này bằng evaluateJavascript. Tập lệnh này ghi đè navigator.credentials.get để chặn yêu cầu và gửi chuỗi JSON OpenID4VP bên trong đến ứng dụng 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");
    }
  };
})();

Triển khai giấy tờ tuỳ thân kỹ thuật số trong Android

Định cấu hình ứng dụng Android để xử lý các yêu cầu về thông tin xác thực được gửi từ bridge.js.

Tạo trình nghe Android

Thiết lập một lớp DigitalCredentialWebListener triển khai WebViewCompat.WebMessageListener. Lớp này xử lý các thông báo hai chiều giữa JavaScript và mã Android gốc, thực thi các bước kiểm tra bảo mật trên isMainFrame và sourceOrigin, đồng thời ngăn chặn các yêu cầu trùng lặp.

Triển khai Trình xử lý trình quản lý thông tin xác thực

Gọi getCredential để yêu cầu thông tin xác thực kỹ thuật số từ thiết bị.

@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"
  }
}

Áp dụng khung cho Hoạt động của bạn

Trong Activity hoặc View, hãy đăng ký WebMessageListener và các hàm liên quan.

// 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")
    }
  },
)

Đang kiểm tra quy trình

Kiểm thử quy trình để xác nhận hoạt động giao tiếp giữa WebView và ứng dụng Android. Bộ chọn thông tin xác thực sẽ xuất hiện trong WebView trong quá trình này và thông tin xác thực đã chọn sẽ quay lại WebView sau khi hoàn tất.