Credential Manager относится к набору API, представленному в Android 14, которые поддерживают несколько методов входа, таких как имя пользователя-пароль, ключи доступа и решения для федеративного входа (например, Sign-in with Google). При вызове API Credential Manager система Android объединяет учетные данные от всех поставщиков учетных данных, установленных на устройстве. В этом документе описывается набор API, которые предоставляют конечные точки интеграции для этих поставщиков учетных данных.
Настраивать
Прежде чем реализовывать функциональность в вашем поставщике учетных данных, выполните шаги по настройке, показанные в следующих разделах.
Объявить зависимости
В файле build.gradle
вашего модуля объявите зависимость, используя последнюю версию библиотеки Credential Manager:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Объявить элемент службы в файле манифеста
В файле манифеста вашего приложения AndroidManifest.xml
включите объявление <service>
для класса службы, который расширяет класс CredentialProviderService
из библиотеки androidx.credentials, как показано в примере ниже.
<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>
Разрешение и фильтр намерений, показанные выше, являются неотъемлемой частью потока Credential Manager для ожидаемой работы. Разрешение необходимо, чтобы только система Android могла привязываться к этой службе. Фильтр намерений используется для обнаружения этой службы как поставщика учетных данных, который будет использоваться Credential Manager.
Объявите поддерживаемые типы учетных данных
В каталоге 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, такими как автозаполнение паролей и других данных. Эти поставщики могут использовать ту же внутреннюю инфраструктуру для хранения существующих типов учетных данных, одновременно расширяя ее для поддержки других, включая пароли.
Двухэтапный подход к взаимодействию с поставщиками
Credential Manager взаимодействует с поставщиками учетных данных в два этапа:
- Первая фаза — это фаза begin/query , в ходе которой система привязывается к службам поставщика учетных данных и вызывает методы
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
илиonClearCredentialStateRequest()
с запросамиBegin…
. Поставщики должны обрабатывать эти запросы и отвечать ответамиBegin…
, заполняя их записями, представляющими визуальные параметры для отображения в селекторе учетных записей. Каждая запись должна иметь установленныйPendingIntent
. - После того, как пользователь выбирает запись, начинается фаза выбора и активируется
PendingIntent
, связанный с записью, что приводит к запуску соответствующей активности поставщика. После того, как пользователь завершает взаимодействие с этой активностью, поставщик учетных данных должен задать ответ на результат активности перед ее завершением. Затем этот ответ отправляется клиентскому приложению, которое вызвало Credential Manager.
Создание ключа доступа
Обработка запросов на создание ключа доступа
Когда клиентское приложение хочет создать ключ доступа и сохранить его у поставщика учетных данных, оно вызывает API createCredential
. Чтобы обработать этот запрос в службе поставщика учетных данных таким образом, чтобы ключ доступа действительно сохранился в вашем хранилище, выполните шаги, показанные в следующих разделах.
- Переопределите метод
onBeginCreateCredentialRequest()
в вашей службе, расширенной отCredentialProviderService
. - Обработайте
BeginCreateCredentialRequest
, создав соответствующийBeginCreateCredentialResponse
и передав его через обратный вызов. - При построении
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
должна соответствовать следующим требованиям:
- Соответствующее действие должно быть настроено для отображения любого необходимого биометрического запроса, подтверждения или выбора.
- Любые требуемые данные, которые требуются поставщику при вызове соответствующей активности, следует задать как дополнительные в намерении, используемом для создания
PendingIntent
, например,accountId
в потоке создания. - Ваш
PendingIntent
должен быть создан с флагомPendingIntent.FLAG_MUTABLE
, чтобы система могла добавить окончательный запрос к дополнительному намерению. - Ваш
PendingIntent
не должен быть создан с флагомPendingIntent.FLAG_ONE_SHOT
так как пользователь может выбрать запись, вернуться и выбрать ее повторно, что приведет к повторному срабатываниюPendingIntent
. - Ваш
PendingIntent
должен быть создан с уникальным кодом запроса, чтобы каждая запись могла иметь свой собственный соответствующийPendingIntent
.
Обработка выбора записи для запросов на создание ключа доступа
- Когда пользователь выбирает ранее заполненный
CreateEntry
, вызывается соответствующийPendingIntent
и создается связанная с нимActivity
поставщика. - После вызова метода
onCreate
вашей Activity получите доступ к связанному намерению и передайте его в классPendingIntentHander
, чтобы получитьProviderCreateCredentialRequest
. - Извлеките
requestJson
,callingAppInfo
иclientDataHash
из запроса. - Извлеките локальный
accountId
из намерения extra. Это пример реализации, специфичной для приложения, и он не является обязательным. Этот идентификатор учетной записи может использоваться для хранения этих учетных данных для этого конкретного идентификатора учетной записи. - Проверьте
requestJson
. В примере ниже используются локальные классы данных, такие какPublicKeyCredentialCreationOptions
, для преобразования входного JSON в структурированный класс согласно спецификации WebAuthn. Как поставщик учетных данных, вы можете заменить это собственным парсером. - Проверьте ссылку на ресурс для вызывающего приложения, если вызов исходит из собственного приложения Android.
- Вывести запрос аутентификации. В примере ниже используется Android Biometric API.
- После успешной аутентификации сгенерируйте
credentialId
и пару ключей . - Сохраните закрытый ключ в локальной базе данных с помощью
callingAppInfo.packageName
. - Создайте ответ JSON API веб-аутентификации , состоящий из открытого ключа и
credentialId
. В примере ниже используются локальные служебные классы, такие какAuthenticatorAttestationResponse
иFidoPublicKeyCredential
, которые помогают создать JSON на основе ранее упомянутой спецификации. Как поставщик учетных данных, вы можете заменить эти классы собственными конструкторами. - Создайте
CreatePublicKeyCredentialResponse
с помощью JSON, сгенерированного выше. - Установите
CreatePublicKeyCredentialResponse
как дополнение кIntent
черезPendingIntentHander.setCreateCredentialResponse()
и установите это намерение в качестве результата Activity. - Завершите занятие.
Пример кода ниже иллюстрирует эти шаги. Этот код должен быть обработан в вашем классе Activity после вызова onCreate()
.
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 для обработки запросов пароля. - При создании
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
, и передайте его в класс PendingIntentHander
, чтобы получить метод ProviderCreateCredentialRequest
.
Пример ниже иллюстрирует, как реализовать этот процесс. Этот код должен быть обработан в методе onCreate()
вашего Activity.
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
как дополнительный параметр в намерении, чтобы знать, с какими учетными данными он сопоставляется, когда пользователь выбирает эту запись.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) ) }
После того как вы запросили и ввели учетные данные, вам необходимо обработать этап выбора учетных данных, выбранных пользователем, будь то ключ доступа или пароль.
Обработка выбора пользователем ключей доступа
- В методе
onCreate
соответствующего Activity извлеките связанное намерение и передайте его вPendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Извлеките
GetPublicKeyCredentialOption
из запроса, полученного выше. Затем извлекитеrequestJson
иclientDataHash
из этой опции. - Извлеките
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 Biometric API.
После успешной аутентификации создайте ответ JSON на основе спецификации W3 Web Authentication Assertion . В приведенном ниже фрагменте кода вспомогательные классы данных, такие как
AuthenticatorAssertionResponse
, используются для получения структурированных параметров и преобразования их в требуемый формат JSON. Ответ содержит цифровую подпись из закрытого ключа учетных данных WebAuthn. Сервер проверяющей стороны может проверить эту подпись для аутентификации пользователя перед входом в систему.Создайте
PublicKeyCredential
, используя сгенерированный выше JSON, и установите его в финальномGetCredentialResponse
. Установите этот финальный ответ в результате этой активности.
Следующий пример иллюстрирует, как можно реализовать эти шаги:
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)
Обработка выбора пользователя для аутентификации по паролю
- В соответствующей активности получите доступ к намерению, переданному в
onCreate
и извлекитеProviderGetCredentialRequest
с помощьюPendingIntentHandler
. Используйте
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
если учетные данные заблокированы. Если пользователь выбирает эту запись, вызывается Activity, соответствующая действию намерения, установленному в PendingIntent
. Затем поставщики учетных данных могут запустить поток биометрической аутентификации или аналогичный механизм для разблокировки учетных данных. В случае успеха поставщик учетных данных должен создать BeginGetCredentialResponse
, аналогично тому, как описана выше обработка входа пользователя , поскольку учетные данные теперь разблокированы. Затем этот ответ должен быть задан с помощью метода PendingIntentHandler.setBeginGetCredentialResponse()
до того, как подготовленное намерение будет установлено в качестве результата и Activity будет завершено.
Очистить запросы на учетные данные
Клиентское приложение может запросить очистку любого состояния, поддерживаемого для выбора учетных данных, например, поставщик учетных данных может запомнить ранее выбранные учетные данные и вернуть их только в следующий раз. Клиентское приложение вызывает этот API и ожидает, что фиксированный выбор будет очищен. Ваша служба поставщика учетных данных может обработать этот запрос, переопределив метод onClearCredentialStateRequest()
:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Добавьте возможность ссылки на страницу настроек вашего провайдера
Чтобы разрешить пользователям открывать настройки вашего провайдера с экрана «Пароли, ключи доступа и автозаполнение» , приложения поставщика учетных данных должны реализовать атрибут манифеста credential-provider
settingsActivity
в res/xml/provider.xml
. Этот атрибут позволяет использовать намерение для открытия экрана собственных настроек вашего приложения, если пользователь нажимает на имя провайдера в списке служб «Пароли, ключи доступа и автозаполнение» . Установите значение этого атрибута на имя действия, которое будет запущено с экрана настроек.
<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>

Настройки намерений
Открыть настройки : Намерение android.settings.CREDENTIAL_PROVIDER
открывает экран настроек, на котором пользователь может выбрать предпочитаемых и дополнительных поставщиков учетных данных.

Предпочтительная служба учетных данных : намерение ACTION_REQUEST_SET_AUTOFILL_SERVICE
перенаправляет пользователя на экран выбора предпочтительного поставщика. Выбранный на этом экране поставщик становится предпочтительным поставщиком учетных данных и автозаполнения.

Получите список разрешенных привилегированных приложений
Привилегированные приложения, такие как веб-браузеры, выполняют вызовы Credential Manager от имени других проверяющих сторон, устанавливая параметр origin
в методах Credential Manager GetCredentialRequest()
и CreatePublicKeyCredentialRequest()
. Для обработки этих запросов поставщик учетных данных извлекает origin
с помощью API getOrigin()
.
Чтобы получить origin
, приложению поставщика учетных данных необходимо передать список привилегированных и доверенных вызывающих абонентов в API androidx.credentials.provider.CallingAppInfo's getOrigin()
. Этот список разрешенных вызовов должен быть допустимым объектом JSON. origin
возвращается, если packageName
и отпечатки сертификата, полученные из signingInfo
совпадают с отпечатками приложения, найденными в privilegedAllowlist
переданном в API getOrigin()
. После получения значения origin
приложение поставщика должно считать это привилегированным вызовом и установить этот origin
в клиентских данных в AuthenticatorResponse
вместо вычисления origin
с использованием подписи вызывающего приложения.
Если вы извлекаете origin
, используйте clientDataHash
, который предоставляется непосредственно в CreatePublicKeyCredentialRequest()
или GetPublicKeyCredentialOption()
вместо сборки и хеширования clientDataJSON
во время запроса подписи. Чтобы избежать проблем с разбором JSON, задайте значение-заполнитель для clientDataJSON
в ответе подтверждения и утверждения. Google Password Manager использует общедоступный список разрешенных вызовов getOrigin()
. Как поставщик учетных данных, вы можете использовать этот список или предоставить свой собственный в формате JSON, описанном API. Поставщик должен выбрать, какой список использовать. Чтобы получить привилегированный доступ с помощью сторонних поставщиков учетных данных, обратитесь к документации, предоставленной третьей стороной.
Включить поставщиков на устройстве
Пользователи должны включить провайдера через настройки устройства > Пароли и учетные записи > Ваш провайдер > Включить или Отключить .
fun createSettingsPendingIntent(): PendingIntent