凭据管理器是指在 Android 14 中引入的一组 API,这些 API 支持多种登录方法,例如用户名密码、密码密钥和联合登录解决方案(例如使用 Google 登录)。调用凭据管理器 API 时,Android 系统会汇总设备上安装的所有凭据提供程序的凭据。本文档描述了为这些凭据提供程序提供集成端点的 API 集。
设置
在您的凭据提供程序中实现功能之前,请完成以下部分中显示的设置步骤。
声明依赖项
在模块的build.gradle
文件中,使用最新版本的凭据管理器库声明依赖项
implementation "androidx.credentials:credentials:1.2.0-{latest}"
在清单文件中声明服务元素
在您的应用清单文件AndroidManifest.xml
中,为扩展 androidx.credentials 库中的CredentialProviderService
类的服务类包含<service>
声明,如下例所示。
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="<any drawable icon>"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
上面显示的权限和意图过滤器对于凭据管理器流程按预期工作至关重要。需要此权限才能仅允许 Android 系统绑定到此服务。意图过滤器用于将此服务发现为凭据管理器要使用的凭据提供程序。
声明受支持的凭据类型
在您的 res/xml
目录下,创建一个名为 provider.xml
的新文件。在此文件中,通过库中为每种凭据类型定义的常量,声明您的服务支持的凭据类型。在下面的示例中,服务支持传统的密码以及密钥,它们的常量定义为 TYPE_PASSWORD_CREDENTIAL
和 TYPE_PUBLIC_KEY_CREDENTIAL
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
在之前的 API 级别上,凭据提供程序与密码和其他数据的自动填充等 API 集成。这些提供程序可以使用相同的内部基础架构来存储现有的凭据类型,同时扩展它以支持其他类型,包括密钥。
提供程序交互的两阶段方法
凭据管理器分两个阶段与凭据提供程序交互
- 第一阶段是开始/查询阶段,系统在此阶段绑定到凭据提供程序服务并调用
onBeginGetCredentialRequest()
、onBeginCreateCredentialRequest()
或onClearCredentialStateRequest()
方法以及Begin…
请求。提供程序必须处理这些请求并使用Begin…
响应进行响应,并使用代表帐户选择器上显示的可视选项的条目填充它们。每个条目都必须设置一个PendingIntent
。 - 一旦用户选择一个条目,选择阶段就会开始,并且与该条目关联的
PendingIntent
会被触发,从而显示相应的提供程序活动。一旦用户完成与该活动的交互,凭据提供程序必须在结束活动之前将响应设置为活动的结果。然后,此响应将发送到调用凭据管理器的客户端应用程序。
处理密钥创建
处理密钥创建查询
当客户端应用程序希望创建密钥并将其与凭据提供程序一起存储时,它们会调用 createCredential
API。为了处理您凭据提供程序服务中的此请求,以便密钥实际存储在您的存储中,请完成以下部分中显示的步骤。
- 覆盖从
CredentialProviderService
扩展的服务中的onBeginCreateCredentialRequest()
方法。 - 通过构造相应的
BeginCreateCredentialResponse
并通过回调传递它来处理BeginCreateCredentialRequest
。 - 在构造
BeginCreateCredentialResponse
时,添加所需的CreateEntries
。每个CreateEntry
应该对应于可以保存凭据的帐户,并且必须设置PendingIntent
以及其他必需的元数据。
以下示例说明如何实现这些步骤。
override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
if (response != null) {
callback.onResult(response)
} else {
callback.onError(CreateCredentialUnknownException())
}
}
fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
}
// Request not supported
return null
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest
): BeginCreateCredentialResponse {
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
val createEntries: MutableList<CreateEntry> = mutableListOf()
createEntries.add( CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
createEntries.add( CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
return BeginCreateCredentialResponse(createEntries)
}
private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
val intent = Intent(action).setPackage(PACKAGE_NAME)
// Add your local account ID as an extra to the intent, so that when
// user selects this entry, the credential can be saved to this
// account
intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
return PendingIntent.getActivity(
applicationContext, UNIQUE_REQ_CODE,
intent, (
PendingIntent.FLAG_MUTABLE
or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
您的 PendingIntent
构造应遵守以下规定
- 相应的 Activity 应设置为显示任何所需的生物识别提示、确认或选择。
- 当调用相应的活动时,提供程序需要的任何必需数据都应作为附加数据设置在用于创建
PendingIntent
的 Intent 上,例如创建流程中的accountId
。 - 您的
PendingIntent
必须使用标志PendingIntent.FLAG_MUTABLE
构造,以便系统可以将最终请求附加到 Intent 附加数据。 - 您的
PendingIntent
不能使用标志PendingIntent.FLAG_ONE_SHOT
构造,因为用户可以选择一个条目,然后返回并重新选择它,这会导致PendingIntent
触发两次。 - 您的
PendingIntent
必须使用唯一的请求代码构造,以便每个条目都可以拥有其自己的PendingIntent
。
处理密钥创建请求的条目选择
- 当用户选择先前填充的
CreateEntry
时,将调用相应的PendingIntent
并创建关联的提供程序Activity
。 - 在调用您的 Activity 的
onCreate
方法后,访问关联的 Intent 并将其传递到PendingIntentHander
类以获取ProviderCreateCredentialRequest
。 - 从请求中提取
requestJson
、callingAppInfo
和clientDataHash
。 - 从 Intent 附加数据中提取本地
accountId
。这是一个示例应用程序特定的实现,不是必需的。此帐户 ID 可用于针对此特定帐户 ID 存储此凭据。 - 验证
requestJson
。下面的示例使用诸如PublicKeyCredentialCreationOptions
之类的本地数据类,根据 WebAuthn 规范将输入 JSON 转换为结构化类。作为凭据提供程序,您可以用您自己的解析器替换它。 - 如果调用来自原生 Android 应用程序,请检查调用应用程序的 asset-link。
- 显示身份验证提示。下面的示例使用 Android 生物识别 API。
- 身份验证成功后,生成
credentialId
和 密钥对。 - 将 私钥 存储在您针对
callingAppInfo.packageName
的本地数据库中。 - 构造一个包含 公钥 和
credentialId
的 Web 身份验证 API JSON 响应。下面的示例使用诸如AuthenticatorAttestationResponse
和FidoPublicKeyCredential
之类的本地实用程序类,这些类有助于根据前面提到的规范构造 JSON。作为凭据提供程序,您可以用您自己的构建器替换这些类。 - 使用上面生成的 JSON 构造
CreatePublicKeyCredentialResponse
。 - 通过
PendingIntentHander.setCreateCredentialResponse()
将CreatePublicKeyCredentialResponse
设置为Intent
上的附加数据,并将该 Intent 设置为 Activity 的结果。 - 完成 Activity。
下面的代码示例说明了这些步骤。此代码需要在调用 onCreate()
后在您的 Activity 类中处理。
val request =
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
val publicKeyRequest: CreatePublicKeyCredentialRequest =
request.callingRequest as CreatePublicKeyCredentialRequest
createPasskey(
publicKeyRequest.requestJson,
request.callingAppInfo,
publicKeyRequest.clientDataHash,
accountId
)
}
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
// Generate a credentialId
val credentialId = ByteArray(32)
SecureRandom().nextBytes(credentialId)
// Generate a credential key pair
val spec = ECGenParameterSpec("secp256r1")
val keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(spec)
val keyPair = keyPairGen.genKeyPair()
// Save passkey in your database as per your own implementation
// Create AuthenticatorAttestationResponse object to pass to
// FidoPublicKeyCredential
val response = AuthenticatorAttestationResponse(
requestOptions = request,
credentialId = credentialId,
credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
origin = appInfoToOrigin(callingAppInfo),
up = true,
uv = true,
be = true,
bs = true,
packageName = callingAppInfo.packageName
)
val credential = FidoPublicKeyCredential(
rawId = credentialId, response = response
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result, createPublicKeyCredResponse
)
setResult(Activity.RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Create passkey for ${request.rp.name}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
}
fun appInfoToOrigin(info: CallingAppInfo): String {
val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
val md = MessageDigest.getInstance("SHA-256");
val certHash = md.digest(cert)
// This is the format for origin
return "android:apk-key-hash:${b64Encode(certHash)}"
}
处理密码创建请求的查询
要处理密码创建请求的查询,请执行以下操作
- 在上一节中提到的
processCreateCredentialRequest()
方法内,在 switch 块中添加另一个 case 来处理密码请求。 - 在构造
BeginCreateCredentialResponse
时,添加所需的CreateEntries
。 - 每个
CreateEntry
应该对应于可以保存凭据的帐户,并且必须在其上设置PendingIntent
以及其他元数据。
以下示例说明如何实现这些步骤
fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest
): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
is BeginCreatePasswordCredentialRequest -> {
// Request is password type
return handleCreatePasswordQuery(request)
}
}
return null
}
private fun handleCreatePasswordQuery(
request: BeginCreatePasswordCredentialRequest
): BeginCreateCredentialResponse {
val createEntries: MutableList<CreateEntry> = mutableListOf()
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
createEntries.add(
CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
createEntries.add(
CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
return BeginCreateCredentialResponse(createEntries)
}
处理密码创建请求的条目选择
当用户选择填充的 CreateEntry
时,相应的 PendingIntent
将执行并显示关联的 Activity。访问在 onCreate
中传递的关联 Intent,并将其传递到 PendingIntentHander
类以获取 ProviderCreateCredentialRequest
方法。
以下示例说明如何实现此过程。此代码需要在您的 Activity 的 onCreate()
方法中处理。
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
PasswordInfo(
request.id,
request.password,
createRequest.callingAppInfo.packageName
)
)
//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()
处理用户登录
用户登录通过以下步骤处理
- 当客户端应用程序尝试 登录用户 时,它会准备一个
GetCredentialRequest
实例。 - Android 框架通过绑定到这些服务将此请求传播到所有适用的凭据提供程序。
- 然后,提供程序服务会收到一个
BeginGetCredentialRequest
,其中包含一个BeginGetCredentialOption
列表,每个列表都包含可用于检索匹配凭据的参数。
要在您的凭据提供程序服务中处理此请求,请完成以下步骤
覆盖
onBeginGetCredentialRequest()
方法以处理请求。请注意,如果您的凭据已锁定,您可以立即在响应中设置AuthenticationAction
并调用回调。private val unlockEntryTitle = "Authenticate to continue" override fun onBeginGetCredentialRequest( request: BeginGetCredentialRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>, ) { if (isAppLocked()) { callback.onResult(BeginGetCredentialResponse( authenticationActions = mutableListOf(AuthenticationAction( unlockEntryTitle, createUnlockPendingIntent()) ) ) ) return } try { response = processGetCredentialRequest(request) callback.onResult(response) } catch (e: GetCredentialException) { callback.onError(GetCredentialUnknownException()) } }
需要在返回任何
credentialEntries
之前解锁凭据的提供程序必须设置一个挂起意图,该意图将用户导航到应用程序的解锁流程private fun createUnlockPendingIntent(): PendingIntent { val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME) return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, ( PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) ) }
从您的本地数据库中检索凭据,并使用
CredentialEntries
设置它们以在选择器上显示。对于密钥,您可以将credentialId
作为附加数据设置在 Intent 上,以便在用户选择此条目时知道它映射到哪个凭据。companion object { // These intent actions are specified for corresponding activities // that are to be invoked through the PendingIntent(s) private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY" private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD" } fun processGetCredentialsRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackage = request.callingAppInfo?.packageName val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackage, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackage, option ) ) ) } else -> { Log.i(TAG, "Request not supported") } } } return BeginGetCredentialResponse(credentialEntries) }
从您的数据库查询凭据,创建密钥和密码条目以填充。
private fun populatePasskeyData( callingAppInfo: CallingAppInfo, option: BeginGetPublicKeyCredentialOption ): List<CredentialEntry> { val passkeyEntries: MutableList<CredentialEntry> = mutableListOf() val request = PublicKeyCredentialRequestOptions(option.requestJson) // Get your credentials from database where you saved during creation flow val creds = <getCredentialsFromInternalDb(request.rpId)> val passkeys = creds.passkeys for (passkey in passkeys) { val data = Bundle() data.putString("credId", passkey.credId) passkeyEntries.add( PublicKeyCredentialEntry( context = applicationContext, username = passkey.username, pendingIntent = createNewPendingIntent( GET_PASSKEY_INTENT_ACTION, data ), beginPublicKeyCredentialOption = option, displayName = passkey.displayName, icon = passkey.icon ) ) } return passkeyEntries } // Fetch password credentials and create password entries to populate to // the user private fun populatePasswordData( callingPackage: String, option: BeginGetPasswordOption ): List<CredentialEntry> { val passwordEntries: MutableList<CredentialEntry> = mutableListOf() // Get your password credentials from database where you saved during // creation flow val creds = <getCredentialsFromInternalDb(callingPackage)> val passwords = creds.passwords for (password in passwords) { passwordEntries.add( PasswordCredentialEntry( context = applicationContext, username = password.username, pendingIntent = createNewPendingIntent( GET_PASSWORD_INTENT ), beginGetPasswordOption = option displayName = password.username, icon = password.icon ) ) } return passwordEntries } private fun createNewPendingIntent( action: String, extra: Bundle? = null ): PendingIntent { val intent = Intent(action).setPackage(PACKAGE_NAME) if (extra != null) { intent.putExtra("CREDENTIAL_DATA", extra) } return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) ) }
查询并填充凭据后,您现在需要处理用户选择的凭据,无论是密钥还是密码。
处理密钥的用户选择
- 在相应 Activity 的
onCreate
方法中,检索关联的 Intent,并传递到PendingIntentHandler.retrieveProviderGetCredentialRequest()
。 - 从上面检索到的请求中提取
GetPublicKeyCredentialOption
。随后,从此选项中提取requestJson
和clientDataHash
。 - 从 Intent 附加数据中提取
credentialId
,该附加数据是在设置相应的PendingIntent
时由凭据提供程序填充的。 - 使用上面访问的请求参数从本地数据库中提取密钥。
使用提取的元数据和用户验证来断言密钥有效。
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest.credentialOption as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo.getString("credId") // Get the saved passkey from your database based on the credential ID // from the publickeyRequest val passkey = <your database>.getPasskey(credIdEnc) // Decode the credential ID, private key and user ID val credId = b64Decode(credIdEnc) val privateKey = b64Decode(passkey.credPrivateKey) val uid = b64Decode(passkey.uid) val origin = appInfoToOrigin(getRequest.callingAppInfo) val packageName = getRequest.callingAppInfo.packageName validatePasskey( publicKeyRequest.requestJson, origin, packageName, uid, passkey.username, credId, privateKey )
要验证用户,请显示生物识别提示(或其他断言方法)。下面的代码片段使用 Android 生物识别 API。
身份验证成功后,请根据W3 Web Authentication Assertion 规范构建 JSON 响应。在下面的代码片段中,使用了诸如
AuthenticatorAssertionResponse
之类的辅助数据类来接收结构化参数并将其转换为所需的 JSON 格式。响应包含来自 WebAuthn 凭据私钥的数字签名。依赖方服务器可以验证此签名,以便在用户登录前对其进行身份验证。使用上面生成的 JSON 构建一个
PublicKeyCredential
,并将其设置在最终的GetCredentialResponse
上。将此最终响应设置为此 Activity 的结果。
以下示例说明了如何实现这些步骤。
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
val response = AuthenticatorAssertionResponse(
requestOptions = request,
credentialId = credId,
origin = origin,
up = true,
uv = true,
be = true,
bs = true,
userHandle = uid,
packageName = packageName
)
val sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(privateKey)
sig.update(response.dataToSign())
response.signature = sig.sign()
val credential = FidoPublicKeyCredential(
rawId = credId, response = response
)
val result = Intent()
val passkeyCredential = PublicKeyCredential(credential.json)
PendingIntentHandler.setGetCredentialResponse(
result, GetCredentialResponse(passkeyCredential)
)
setResult(RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Use passkey for ${request.rpId}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
处理用户对密码身份验证的选择
- 在相应的 Activity 中,访问传递给
onCreate
的 Intent,并使用PendingIntentHandler
提取ProviderGetCredentialRequest
。 在请求中使用
GetPasswordOption
来检索传入包名的密码凭据。val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption val username = passwordOption.username // Fetch the credentials for the calling app package name val creds = <your_database>.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext() == true) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }
检索完成后,设置所选密码凭据的响应。
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
处理身份验证操作项的选择
如前面所述,如果凭据被锁定,凭据提供程序可以设置AuthenticationAction
。如果用户选择此项,则会调用PendingIntent
中设置的 Intent 操作对应的 Activity。然后,凭据提供程序可以显示生物识别身份验证流程或类似机制来解锁凭据。成功后,凭据提供程序必须构建一个BeginGetCredentialResponse
,类似于上面描述的用户登录处理方式,因为凭据现在已解锁。然后,必须通过PendingIntentHandler.setBeginGetCredentialResponse()
方法设置此响应,然后将准备好的 Intent 设置为结果,并完成 Activity。
清除凭据请求
客户端应用可能会请求清除为凭据选择维护的任何状态,例如,凭据提供程序可能会记住先前选择的凭据,并在下次仅返回该凭据。客户端应用调用此 API 并期望清除粘性选择。您的凭据提供程序服务可以通过覆盖onClearCredentialStateRequest()
方法来处理此请求。
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
添加链接到您的提供程序设置页面的功能
为了允许用户从“**密码、密钥和自动填充**”屏幕打开您的提供程序设置,凭据提供程序应用应在res/xml/provider.xml
中实现credential-provider
settingsActivity
清单属性。此属性允许您使用 Intent 打开应用自己的设置屏幕,如果用户单击“**密码、密钥和自动填充**”服务列表中的提供程序名称。将此属性的值设置为要从设置屏幕启动的 Activity 的名称。
<credential-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsSubtitle="Example settings provider name"
android:settingsActivity="com.example.SettingsActivity">
<capabilities>
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
设置 Intent
打开设置:android.settings.CREDENTIAL_PROVIDER
Intent 会调出一个设置屏幕,用户可以在其中选择其首选和附加的凭据提供程序。
首选凭据服务:ACTION_REQUEST_SET_AUTOFILL_SERVICE
Intent 会将用户重定向到首选提供程序选择屏幕。此屏幕上选择的提供程序将成为首选凭据和自动填充提供程序。
获取特权应用的白名单
诸如 Web 浏览器之类的特权应用代表其他依赖方进行凭据管理器调用,方法是在凭据管理器GetCredentialRequest()
和CreatePublicKeyCredentialRequest()
方法中设置origin
参数。要处理这些请求,凭据提供程序使用getOrigin()
API 检索origin
。
要检索origin
,凭据提供程序应用需要将特权和受信任的调用者列表传递给androidx.credentials.provider.CallingAppInfo's getOrigin()
API。此白名单必须是有效的 JSON 对象。如果从signingInfo
获得的packageName
和证书指纹与在传递给getOrigin()
API 的privilegedAllowlist
中找到的应用的那些指纹匹配,则返回origin
。获得origin
值后,提供程序应用应将其视为特权调用,并在客户端数据中的AuthenticatorResponse
上设置此origin
,而不是使用调用应用的签名计算origin
。
如果检索到origin
,请使用CreatePublicKeyCredentialRequest()
或GetPublicKeyCredentialOption()
中直接提供的clientDataHash
,而不是在签名请求期间组装和散列clientDataJSON
。为避免 JSON 解析问题,请在证明和断言响应中为clientDataJSON
设置占位符值。Google 密码管理器对getOrigin()
的调用使用公开可用的白名单。作为凭据提供程序,您可以使用此列表或以 API 描述的 JSON 格式提供您自己的列表。由提供程序选择使用哪个列表。要获得对第三方凭据提供程序的特权访问,请参考第三方提供的文档。
在设备上启用提供程序
用户必须通过**设备设置 > 密码和帐户 > 您的提供程序 > 启用或禁用**来启用提供程序。
fun createSettingsPendingIntent(): PendingIntent