Менеджер учетных данных — это набор API, представленных в Android 14, которые поддерживают несколько методов входа в систему, таких как имя пользователя и пароль, ключи доступа и решения для федеративного входа (например, вход через Google). При вызове API Менеджера учетных данных система Android собирает учетные данные от всех поставщиков учетных данных, установленных на устройстве. В этом документе описывается набор API, предоставляющих конечные точки интеграции для этих поставщиков учетных данных.
Настраивать
Прежде чем внедрять функциональность в ваш поставщик учетных данных, выполните шаги настройки, описанные в следующих разделах.
Объявление зависимостей
Добавьте следующие зависимости в скрипт сборки вашего модуля приложения, чтобы использовать последнюю версию библиотеки Credential Manager:
Котлин
dependencies { implementation("androidx.credentials:credentials:1.6.0-rc01") }
Круто
dependencies { implementation "androidx.credentials:credentials:1.6.0-rc01" }
Объявите элемент сервиса в файле манифеста.
В файле манифеста вашего приложения AndroidManifest.xml добавьте объявление <service> для класса службы, который наследует класс ` CredentialProviderService из библиотеки `androidx.credentials`, как показано в следующем примере.
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="@mipmap/ic_launcher"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
tools:targetApi="upside_down_cake">
<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 :
<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связанное с записью, вызывая соответствующую активность поставщика учетных данных. После завершения взаимодействия пользователя с этой активностью поставщик учетных данных должен установить ответ на результат активности, прежде чем завершить ее. Этот ответ затем отправляется клиентскому приложению, которое вызвало менеджер учетных данных.
Обработка создания паролей
Обработка запросов на создание паролей
Когда клиентское приложение хочет создать пароль и сохранить его в поставщике учетных данных, оно вызывает 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, чтобы система могла добавить окончательный запрос к дополнительному содержимому объекта Intent. - В вашем
PendingIntentне следует использовать флагPendingIntent.FLAG_ONE_SHOT, поскольку пользователь может выбрать запись, вернуться и выбрать ее снова, что приведет к двойному срабатываниюPendingIntent. - Ваш
PendingIntentдолжен быть сформирован с уникальным кодом запроса, чтобы каждая запись могла иметь свой собственныйPendingIntent.
Обработка выбора элементов для запросов на создание паролей
- Когда пользователь выбирает ранее заполненную
CreateEntry, вызывается соответствующийPendingIntentи создается связанная с нимActivityпоставщика. - После вызова метода
onCreateвашей Activity получите доступ к связанному с ней намерению и передайте его в классPendingIntentHander, чтобы получитьProviderCreateCredentialRequest. - Извлеките из запроса данные
requestJson,callingAppInfoиclientDataHash. - Извлеките локальный
accountIdиз дополнительного параметра Intent. Это пример реализации, специфичный для конкретного приложения, и он не является обязательным. Этот accountId можно использовать для хранения учетных данных для данного конкретного accountId. - Проверьте
requestJson. В приведенном ниже примере используются локальные классы данных, такие какPublicKeyCredentialCreationOptionsдля преобразования входного JSON в структурированный класс в соответствии со спецификацией WebAuthn. В качестве поставщика учетных данных вы можете заменить это собственным парсером. - Если звонок совершается из нативного приложения Android, проверьте ссылку на ресурс для вызывающего приложения.
- Отобразить запрос на аутентификацию. В приведенном ниже примере используется Android Biometric API.
- После успешной аутентификации сгенерируйте пару
credentialIdи ключей (key pair ). - Сохраните закрытый ключ в локальной базе данных, указав в нем параметр
callingAppInfo.packageName. - Создайте JSON-ответ API веб-аутентификации , состоящий из открытого ключа и
credentialId. В приведенном ниже примере используются локальные вспомогательные классы, такие какAuthenticatorAttestationResponseиFidoPublicKeyCredential, которые помогают создать JSON на основе упомянутой ранее спецификации. В качестве поставщика учетных данных вы можете заменить эти классы своими собственными конструкторами. - Создайте объект
CreatePublicKeyCredentialResponseиспользуя сгенерированный выше JSON-файл. - Добавьте
CreatePublicKeyCredentialResponseв качестве дополнительного параметра вIntentс помощьюPendingIntentHander.setCreateCredentialResponse()и установите для этого Intent результат выполнения Activity. - Завершите задание.
Приведённый ниже пример кода иллюстрирует эти шаги. Этот код необходимо обработать в вашем классе Activity после вызова onCreate() .
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
// ...
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
)
}
}
@SuppressLint("RestrictedApi")
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
{ }, // Pass in your own executor
object : AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
@RequiresApi(VERSION_CODES.P)
override fun onAuthenticationSucceeded(
result: 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result,
createPublicKeyCredResponse
)
setResult(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)
}
@RequiresApi(VERSION_CODES.P)
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()упомянутого в предыдущем разделе, добавьте еще один case внутри блока 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
}
@RequiresApi(VERSION_CODES.M)
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. Получите доступ к связанному Intent, переданному в onCreate , и передайте его в класс PendingIntentHander чтобы получить метод ProviderCreateCredentialRequest .
Приведённый ниже пример иллюстрирует, как реализовать этот процесс. Этот код необходимо обработать в методе onCreate() вашего Activity.
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (createRequest == null) {
return
}
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
mDatabase.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)
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 processGetCredentialRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackageInfo = request.callingAppInfo val callingPackageName = callingPackageInfo?.packageName.orEmpty() val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackageName, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackageInfo, 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 ), beginGetPublicKeyCredentialOption = 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 получите связанный с ней Intent и передайте его в методPendingIntentHandler.retrieveProviderGetCredentialRequest(). - Извлеките параметр
GetPublicKeyCredentialOptionиз полученного выше запроса. Затем извлеките из этого параметраrequestJsonиclientDataHash. - Извлеките
credentialIdиз параметра IntentExtra, который был заполнен поставщиком учетных данных при настройке соответствующегоPendingIntent. - Извлеките пароль из локальной базы данных, используя параметры запроса, указанные выше.
Убедитесь, что пароль действителен, используя извлеченные метаданные и подтверждение пользователя.
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo?.getString("credId").orEmpty() // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest val passkey = mDatabase.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 )Для проверки личности пользователя отобразите запрос на биометрическую идентификацию (или используйте другой метод подтверждения). Приведенный ниже фрагмент кода использует API биометрической идентификации Android.
После успешной аутентификации сформируйте JSON-ответ на основе спецификации W3 Web Authentication Assertion . В приведенном ниже фрагменте кода используются вспомогательные классы данных, такие как
AuthenticatorAssertionResponse, для приема структурированных параметров и преобразования их в требуемый формат JSON. Ответ содержит цифровую подпись, полученную из закрытого ключа учетных данных WebAuthn. Сервер проверяющей стороны может проверить эту подпись для аутентификации пользователя перед входом в систему.Создайте объект
PublicKeyCredentialиспользуя сгенерированный выше JSON, и установите его в итоговомGetCredentialResponse. Установите этот итоговый ответ в качестве результата данной операции.
Следующий пример иллюстрирует, как можно реализовать эти шаги:
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
{ }, // Pass in your own 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
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?.credentialOptions?.first() as GetPasswordOption val username = passwordOption.allowedUserIds.first() // Fetch the credentials for the calling app package name val creds = mDatabase.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext()) { 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: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>
) {
// Delete any maintained state as appropriate.
}
Добавьте возможность перехода на страницу настроек вашего провайдера.
Чтобы пользователи могли открывать настройки вашего поставщика учетных данных с экрана «Пароли, ключи доступа и автозаполнение» , приложения, предоставляющие учетные данные, должны реализовать атрибут манифеста credential-provider settingsActivity в файле res/xml/provider.xml . Этот атрибут позволяет использовать Intent для открытия экрана настроек вашего приложения, если пользователь щелкнет по имени поставщика в списке служб «Пароли, ключи доступа и автозаполнение ». Установите значение этого атрибута равным имени действия, которое будет запущено с экрана настроек.
<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 перенаправляет пользователя на экран выбора предпочтительного поставщика. Выбранный на этом экране поставщик становится предпочтительным поставщиком учетных данных и автозаполнения.

Получите список разрешенных приложений.
Привилегированные приложения, такие как веб-браузеры, выполняют вызовы диспетчера учетных данных от имени других заинтересованных сторон, устанавливая параметр origin в методах диспетчера учетных данных 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