Менеджер учетных данных – это набор API, представленный в Android 14 и поддерживающий несколько способов входа, например с помощью имени пользователя и пароля, ключей доступа и федеративных решений (таких как "Войти с аккаунтом Google"). При вызове Credential Manager API система Android собирает учетные данные от всех поставщиков, установленных на устройстве. В этом документе описан набор API, которые предоставляют конечные точки интеграции для этих поставщиков учетных данных.
Настроить
Прежде чем реализовывать функции в поставщике учетных данных, выполните действия по настройке, описанные в следующих разделах.
Объявление зависимостей
Чтобы использовать последнюю версию библиотеки Менеджера учетных данных, добавьте в скрипт сборки модуля приложения следующие зависимости:
Котлин
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha03") }
Круто
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha03" }
Объявите элемент службы в файле манифеста
В файле манифеста приложения 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>
Разрешение и фильтр интентов, показанные в предыдущем примере, необходимы для правильной работы Менеджера учетных данных. Разрешение необходимо, чтобы к сервису могла подключаться только система Android. Фильтр интентов используется для обнаружения этого сервиса как поставщика учетных данных, который будет использоваться Менеджером учетных данных.
Объявление поддерживаемых типов учетных данных
В каталоге 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, чтобы система могла добавить окончательный запрос в дополнительные данные намерения. - Не создавайте
PendingIntentс флагомPendingIntent.FLAG_ONE_SHOT, поскольку пользователь может выбрать запись, вернуться назад и выбрать ее снова, что приведет к двойному запускуPendingIntent. - Объект
PendingIntentдолжен быть создан с уникальным кодом запроса, чтобы у каждой записи был свой объектPendingIntent.
Как обрабатывать выбор записи для запросов на создание ключа доступа
- Когда пользователь выбирает ранее заполненный
CreateEntry, вызывается соответствующийPendingIntentи создается связанный поставщикActivity. - После вызова метода
onCreateвашего класса Activity получите доступ к связанному намерению и передайте его в классPendingIntentHander, чтобы получитьProviderCreateCredentialRequest. - Извлеките из запроса значения
requestJson,callingAppInfoиclientDataHash. - Извлеките локальный
accountIdиз дополнительных данных намерения. Это реализация, характерная для примера приложения, и она не является обязательной. Этот идентификатор аккаунта можно использовать для хранения этих учетных данных для этого конкретного идентификатора аккаунта. - Проверьте
requestJson. В примере ниже используются локальные классы данных, такие какPublicKeyCredentialCreationOptions, чтобы преобразовать входной JSON в структурированный класс в соответствии со спецификацией WebAuthn. Как поставщик учетных данных, вы можете заменить его собственным синтаксическим анализатором. - Если вызов поступает из нативного приложения Android, проверьте ссылку на объект для вызывающего приложения.
- Показать окно аутентификации. В примере ниже используется Android Biometric API.
- После успешной аутентификации создайте
credentialIdи пару ключей. - Сохраните закрытый ключ в локальной базе данных, сопоставив его с
callingAppInfo.packageName. - Создайте ответ JSON для Web Authentication API, который состоит из открытого ключа и
credentialId. В примере ниже используются локальные служебные классы, такие какAuthenticatorAttestationResponseиFidoPublicKeyCredential, которые помогают создать JSON на основе упомянутой ранее спецификации.Как поставщик учетных данных, вы можете заменить эти классы собственными конструкторами. - Создайте объект
CreatePublicKeyCredentialResponseс помощью сгенерированного выше кода JSON. - Задайте
CreatePublicKeyCredentialResponseв качестве дополнительного параметра дляIntentс помощьюPendingIntentHander.setCreateCredentialResponse()и установите этот интент в качестве результата 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(), упомянутый в предыдущем разделе, добавьте ещё один вариант в блок 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 и открывается связанное действие. Получите доступ к связанному намерению, переданному в 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в качестве дополнительного параметра намерения, чтобы знать, с какими учетными данными он сопоставляется, когда пользователь выбирает эту запись.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из дополнительных данных намерения, которые были заполнены поставщиком учетных данных при настройке соответствующего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 )Чтобы подтвердить личность пользователя, покажите запрос на биометрическую аутентификацию (или другой метод подтверждения). В приведенном ниже фрагменте кода используется 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,
{ }, // 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. Этот атрибут позволяет использовать намерение, чтобы открыть экран настроек приложения, если пользователь нажмет на название поставщика в списке сервисов Пароли, ключи доступа и автозаполнение. Задайте для атрибута значение, соответствующее названию действия, которое будет запускаться с экрана настроек.
<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, переданном в getOrigin() API. После получения значения origin приложение-поставщик должно считать этот вызов привилегированным и установить это значение origin в данных клиента в AuthenticatorResponse, а не вычислять origin с помощью подписи вызывающего приложения.
Если вы получаете origin, используйте clientDataHash, который предоставляется непосредственно в CreatePublicKeyCredentialRequest() или GetPublicKeyCredentialOption(), вместо того чтобы собирать и хешировать clientDataJSON во время запроса подписи. Чтобы избежать проблем с синтаксическим анализом JSON, задайте значение-плейсхолдер для clientDataJSON в ответе на подтверждение и утверждение. Google Менеджер паролей использует общедоступный белый список для вызовов getOrigin(). Как поставщик учетных данных, вы можете использовать этот список или предоставить собственный в формате JSON, описанном в API. Поставщик сам выбирает, какой список использовать. Чтобы получить привилегированный доступ с помощью сторонних поставщиков учетных данных, ознакомьтесь с документацией, предоставленной третьей стороной.
Как включить поставщиков на устройстве
Пользователям нужно включить поставщика в настройках устройства > Пароли и аккаунты > Поставщик > Включить или отключить.
fun createSettingsPendingIntent(): PendingIntent