本指南详细介绍了如何使用 DigitalCredential API 获取用户的已验证电话号码。此过程包含两个步骤:
- 预检查:通过查询 Android Telephony API,检查用户的运营商是否支持此 API。如果运营商不受支持,请回退到常规短信流程。
- 请求
TS.43 token:您的客户端应用(“验证器”)向用户的设备请求临时 TS.43 令牌。TS.43 token是运营商颁发的凭据,代表用户的身份。 - 用令牌换取电话号码:应用的后端会与聚合器或运营商交换
TS.43 token,以获取用户已验证的电话号码。
Android 版本兼容性
Android 10(API 级别 29)及更高版本支持电话号码验证 API。
前提条件
如需使用 DigitalCredential API 实现电话号码验证,您需要拥有聚合器或移动运营商的账号。聚合器与运营商互动,并为您的应用提供必要的 API 接口,通常作为可结算的云 API 端点。在本指南中,聚合器和运营商统称为聚合器。
您还需要将以下依赖项添加到 Gradle build 脚本中:
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" }
实现
在调用 Digital Credentials API 之前,请使用 Android Telephony API 检查用户的运营商是否支持此功能。
将 READ_PHONE_STATE 权限添加到 AndroidManifest.xml 文件中。
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val carrierId = telephonyManager.getSimCarrierId()
使用此 ID,通过聚合器后端验证 TS.43 支持。 如果不支持此用户的运营商,您可以回退到备用方法(例如短信),因为调用 Digital Credentials API 会返回空结果。否则,请继续执行数字凭证流程。
端到端流程通常遵循以下步骤:
- 向聚合器请求 DCQL(数字凭证查询语言)参数:调用一个或多个聚合器,并请求一组 DCQL 参数。借助 DCQL,您可以指定从每个聚合器中获取的确切数字凭证。
创建 OpenID4VP 请求:从应用的后端创建 OpenID4VP 请求,同时添加来自聚合器的 DCQL 参数。然后,将 OpenID4VP 请求发送到您的客户端应用。
调用 Credential Manager API:在客户端应用中,使用 Credential Manager API 向操作系统发送 OpenID4VP 请求。作为响应,您会收到一个包含
TS.43 Digital Credential的 OpenID4VP 响应对象。此凭据已加密,只能由关联的聚合器解密。收到运营商令牌后,将客户端应用的响应发送到应用的后端。验证响应:在应用的后端,验证 OpenID4VP 响应。
换取电话号码:从应用的后端将
TS.43 Digital Credential发送给聚合器。聚合器会验证凭据并返回经过验证的电话号码。
向聚合工具请求 DCQL 参数
从应用的后端向聚合器发送请求,以获取数字凭证查询语言 (DCQL) 凭证对象。请务必在请求中提供 Nonce 和请求 ID。聚合器会返回 DCQL 凭证对象,其结构类似于以下内容:
{
// The credential ID is mapped to the request ID that is sent in your request to the aggregator.
"id": "aggregator1",
"format": "dc-authorization+sd-jwt",
"meta": {
"vct_values": [
"number-verification/device-phone-number/ts43"
],
"credential_authorization_jwt": "..."
},
"claims": [
{
"path": ["subscription_hint"],
"values": [1]
},
{
"path": ["phone_number_hint"],
"values": ["+14155552671"]
}
]
}
创建 OpenID4VP 请求
首先,在应用的后端,通过将 DCQL 凭证对象放入嵌套在 dcql_query 对象中的 credentials 数组来创建 dcql_query 对象,如以下示例所示:
"dcql_query": {
"credentials": [
"id": "aggregator1",
"format": "dc-authorization+sd-jwt",
"meta": {
"vct_values": [
"number-verification/device-phone-number/ts43"
],
"credential_authorization_jwt": "..."
},
"claims": [
{
"path": ["subscription_hint"],
"values": [1]
},
{
"path": ["phone_number_hint"],
"values": ["+14155552671"]
}
]
]
}
然后,创建具有以下结构的 OpenID4VP 请求:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "...",
"dcql_query": { ... }
}
}
protocol:对于电话号码验证请求,必须设置为openid4vp-v1-unsigned。response_type和response_mode:表示请求格式的常量,其固定值分别为vp_token和dc_api。nonce:由您的后端为每个请求生成的唯一值。聚合器 DCQL 凭证对象中的 Nonce 必须与此 Nonce 匹配。dcql_query:在这种情况下,请使用dcql_query指定正在请求TS.43 Digital Credential。您也可以在此处申请其他数字凭证。
然后,将 OpenID4VP 请求封装在 DigitalCredential API 请求对象中,并将其发送到客户端应用。
{
"requests":
[
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "...",
"dcql_query": { ... }
}
}
]
}
以下代码段演示了如何生成 DigitalCredential API 请求:
def GenerateDCRequest():
credentials = []
aggregator1_dcql = call_aggregator_endpoint(nonce, "aggregator1", additional_params)
credentials.append(aggregator1_dcql) # You can optionally work with multiple
# aggregators, or request other types of credentials
val dc_request =
{
"requests":
[
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "...",
"dcql_query": {"credentials": credentials}
}
}
]
}
return dc_request
调用 Credential Manager API
在客户端应用中,使用应用的后端提供的 DigitalCredential API 请求调用 Credential Manager API。
val requestJson = generateTs43DigitalCredentialRequestFromServer()
val digiCredOption = GetDigitalCredentialOption(requestJson = requestJson)
val getCredRequest = GetCredentialRequest(
listOf(digiCredOption)
)
coroutineScope.launch {
try {
val response = credentialManager.getCredential(
context = activityContext,
request = getCredRequest
)
val credential = response.credential
when (credential) {
is DigitalCredential -> {
val responseJson = credential.credentialJson
validateResponseOnServer(responseJson)
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential ${credential.type}")
}
}
} catch (e : GetCredentialException) {
// If user cancels the operation, the feature isn't available, or the
// SIM doesn't support the feature, a GetCredentialCancellationException
// will be returned. Otherwise, a GetCredentialUnsupportedException will
// be returned with details in the exception message.
handleFailure(e)
}
}
DigitalCredential API 响应包含 OpenID4VP 响应。DigitalCredential 结果中的典型凭证 JSON 如下所示:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"vp_token": {
"aggregator1": ["eyJhbGciOiAiRVMy..."] # The encrypted TS.43 Digital
# Credential in an array structure.
}
}
}
从客户端应用将 DigitalCredential API 响应发送回后端服务器,以便在后端服务器中进行验证,并用于与聚合器交换经过验证的电话号码。
在某些情况下,响应可能包含 TS.43 错误。错误响应是一个 JSON 对象,遵循 OpenID4VP 错误响应格式:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"error": "<error_code>",
"error_description": "<Human-readable description of the error>",
}
}
error_code 有 2 个可能的值:
invalid_request:这表示请求格式不正确。server_error:这表示处理请求时出现故障。这可能是本地错误或 TS.43 问题。
error_description 字段提供了有关问题的更多详细信息。
验证数字凭证响应
以下示例展示了如何在应用的后端解析响应并执行验证步骤:
def processDigitalCredentialsResponse(response):
# Step 1: Parse out the TS.43 Digital Credential from the response
openId4VpResponse = response['data']
ts43_digital_credential = response['vp_token']["aggregator1"][0]
# Step 2: Perform response validation
verifyResponse(ts43_digital_credential)
def verifyResponse(ts43_digital_credential):
# The returned ts43_digital_credential is an SD-JWT-based Verifiable Credentials
# (SD-JWT VC) as defined in this IETF spec. The section 3.4 of the specification
# outlines how to validate the credential. At a high level, the steps involves
# validating (1) the nonce in the response credential matches the one in the
# request, (2) the integrity of the credential by checking the credential is
# signed by the trusted issuer Android Telephony, and (3) other validity
# properties associated with this credential, such as issue time and expiration
# time
# In most cases, you can use an SD-JWT VC library to perform these validations.
# Some aggregators may also perform the validation logic for you. Check with your
# aggregator to decide the exact scope of the validation required.
换取电话号码
从应用的后端,将经过验证的 TS.43 Digital Credential 发送到聚合器的端点,以验证凭证并接收经过验证的电话号码。
def processDigitalCredentialsResponse(response):
# ... prior steps
# Step 3: Call aggregator endpoint to exchange the verified phone number
callAggregatorPnvEndpoint(ts43_digital_credential)