本指南介绍了如何通过 OpenID for Verifiable Presentations (OpenID4VP) 请求使用数字凭据验证器 API 实现经过验证的电子邮件检索。
添加依赖项
在应用的 build.gradle 文件中,添加以下 Credential Manager 依赖项:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
初始化 Credential Manager
使用应用或 activity 上下文创建 CredentialManager 对象。
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
构建数字凭证请求
如需请求经过验证的电子邮件地址,请构建包含 GetDigitalCredentialOption 的 GetCredentialRequest。此选项需要一个格式为 OpenID 可验证演示 (OpenID4VP) 请求的 requestJson 字符串。
OpenID4VP 请求 JSON 必须遵循特定结构。当前提供商支持具有外部 "digital": {"requests":
[...]} 封装容器的 JSON 结构。
val nonce = generateSecureRandomNonce()
// This request follows the OpenID4VP spec
val openId4vpRequest = """
{
"requests": [
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "$nonce",
"dcql_query": {
"credentials": [
{
"id": "user_info_query",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["UserInfoCredential"]
},
"claims": [
{"path": ["email"]},
{"path": ["name"]},
{"path": ["given_name"]},
{"path": ["family_name"]},
{"path": ["picture"]},
{"path": ["hd"]},
{"path": ["email_verified"]}
]
}
]
}
}
}
]
}
"""
val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
val request = GetCredentialRequest(listOf(getDigitalCredentialOption))
该请求包含以下关键信息:
DCQL 查询:
dcql_query指定了凭据类型和所请求的声明 (email_verified)。您可以请求其他声明来确定验证级别。以下是一些可能的声明:email_verified:在响应中,这是一个布尔值,用于指示电子邮件是否已验证。hd(托管域名):在响应中,此字段为空。
如果电子邮件地址不是 @gmail.com,则 Google 在创建 Google 账号时验证了此电子邮件地址,但没有新鲜度声明。因此,对于非 Google 电子邮件地址,您应考虑使用额外的验证方式(例如动态密码)来验证用户。如需了解凭据的架构以及验证
email_verified等字段的具体规则,请参阅 Google Identity 指南。nonce:为每个请求生成一个唯一的加密安全随机值。这对于安全性至关重要,因为它可以防止重放攻击。
UserInfoCredential:此值表示包含用户属性的特定类型的数字凭据。在请求中包含此参数对于区分电子邮件验证用例至关重要。
接下来,将 openId4vpRequest JSON 封装在 GetDigitalCredentialOption 中,创建 GetCredentialRequest,然后调用 getCredential()。
向用户显示请求
使用 Credential Manager 内置界面向用户显示请求。
try {
// Requesting Digital Credential from user...
val result = credentialManager.getCredential(activity, request)
when (val credential = result.credential) {
is DigitalCredential -> {
val responseJsonString = credential.credentialJson
// Successfully received digital credential response.
// Next, parse this response and send it to your server.
// ...
}
else -> {
// handle Unexpected State() - Up to the developer
}
}
} catch (e: Exception) {
// handle exceptions - Up to the developer
}
在客户端上解析响应
收到响应后,您可以在客户端上执行初步解析。 这对于立即更新界面非常有用,例如显示用户的姓名。
以下代码提取了原始的选择性披露 JWT (SD-JWT),并使用辅助程序对其声明进行解码。
// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val vpToken = responseData.getJSONObject("vp_token")
// 2. Extract the raw SD-JWT string
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)
// 3. Use your parser to get the verified claims
// Server-side validation/parsing is highly recommended.
// Assumes a local parser like the one in our SdJwtParser.kt sample
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")
// 4. Create your VerifiedUserInfo object with REAL data
val userInfo = VerifiedUserInfo(
email = claims.getString("email"),
displayName = claims.optString("name", claims.getString("email"))
)
处理响应
Credential Manager API 将返回 DigitalCredential 响应。
以下示例展示了原始 responseJsonString 的外观,以及在解析内部 SD-JWT 后声明的外观,其中您还会获得其他元数据以及经过验证的电子邮件地址:
/*
// Example of the raw JSON response from credential.credentialJson:
{
"vp_token": {
// This key matches the 'id' you set in your dcql_query
"user_info_query": [
// The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
"eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
]
}
}
// Example of the parsed and verified claims from the SD-JWT on your server:
{
"cnf": {
"jwk": {..}
},
"exp": 1775688222,
"iat": 1775083422,
"iss": "https://verifiablecredentials-pa.googleapis.com",
"vct": "UserInfoCredential",
"email": "jane.doe.246745@gmail.com",
"email_verified": true,
"given_name": "Jane",
"family_name": "Doe",
"name": "Jane Doe",
"picture": "http://example.com/janedoe/me.jpg",
"hd": ""
}
*/
账号创建的服务器端验证
由于检索到的电子邮件经过加密验证,您可以省略电子邮件 OTP 验证步骤,从而显著减少注册摩擦并可能提高转化率。此过程最好在服务器上处理。客户端将原始响应(包含 vp_token)和原始 Nonce 发送到新的服务器端点。
为了进行验证,您的应用必须将完整的 responseJsonString 发送到您的服务器以进行加密验证,然后才能创建账号或让用户登录。
数字凭据可为您的服务器提供两个关键的验证级别:
- 数据的真实性:验证签发者 (
iss) 网址和SD-JWT签名可证明此数据是由可信的授权机构签发的。 - 演示者的身份:验证
cnf字段和密钥绑定 (kb) 签名可确认凭据是由最初向其颁发的同一设备共享的,从而防止凭据被拦截或在其他设备上使用。
服务器上的验证必须实现以下目标:
- 验证签发者:确保
iss(签发者)字段与https://verifiablecredentials-pa.googleapis.com一致。 - 验证签名:使用 https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks 提供的公钥 (JWK) 检查 SD-JWT 的签名。
为确保完全安全,请务必验证 nonce 以防范重放攻击。
通过结合使用这些步骤,您的服务器可以验证数据的真实性和演示者的身份,确保在预配新账号之前,凭据未被拦截或仿冒。
try {
// Send the raw credential response and the original nonce to your server.
// Your server must validate the response. createAccountWithVerifiedCredentials
// is a custom implementation per each RP for server side verification and account creation.
val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)
// Server returns the new account info (e.g., email, name)
val claims = JSONObject(serverResponse.json)
val userInfo = VerifiedUserInfo(
email = claims.getString("email"),
displayName = claims.optString("name", claims.getString("email"))
)
// handle response - Up to the developer
} catch (e: Exception) {
// handle exceptions - Up to the developer
}
创建通行密钥
在预配账号后,建议您立即为该账号创建通行密钥,虽然这是可选步骤,但我们强烈建议您执行。这样可以为用户提供一种安全的无密码登录方法。此流程与标准通行密钥注册流程完全相同。
WebView 支持
为了让该流程在 WebView 上正常运行,开发者应实现 JavaScript 桥接 (JS Bridge) 以方便移交。此桥接器允许 WebView 向原生应用发出信号,然后原生应用可以对 Credential Manager API 执行实际调用。