本指南介绍了如何通过 OpenID for Verifiable Presentations (OpenID4VP) 请求,使用 Digital Credentials Verifier API 实现经过验证的电子邮件地址检索。
添加依赖项
在应用的 build.gradle 文件中,为 Credential Manager 添加以下依赖项:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha01") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha01") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha01" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha01" }
初始化 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 for Verifiable Presentations (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": ""
}
*/
账号创建的服务器端验证
由于检索到的电子邮件地址经过加密验证,因此您可以省略电子邮件地址动态密码验证步骤,从而显著减少注册摩擦并可能提高转化率。此过程最好在服务器上处理。客户端会将原始响应(包含 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 执行实际调用。