API-интерфейсы Credentials Transfer в Credential Manager обеспечивают безопасную передачу учетных данных пользователя между поставщиками учетных данных в пределах одного устройства. В этом руководстве подробно описано, как поставщики учетных данных на Android могут интегрироваться с API-интерфейсами, предоставляемыми библиотекой androidx.credentials:providerevents . Эта функция поддерживает пароли, ключи доступа, адресную информацию и пользовательские поля, используя стандартизированный формат обмена учетными данными FIDO (CXF) .
Основные концепции
Данная система передачи учетных данных упрощает передачу учетных данных между устройствами одной сети без раскрытия исходных данных операционной системе Android или неаутентифицированным приложениям.
Данная структура определяет две основные роли:
- Экспортер (поставщик исходных данных): Поставщик учетных данных, который в данный момент хранит учетные данные пользователя. Он предварительно регистрирует метаданные о доступных для экспорта учетных записях (
ExportEntry) в системе и отвечает на запросы на передачу, когда пользователь выбирает их. - Импортер (поставщик клиентских данных или мастер настройки): Поставщик учетных данных или мастер настройки, инициирующий запрос на импорт (
ImportCredentialsRequest), указывающий, какие типы учетных данных и расширений он может получить.
совместимость с версиями Android
API для передачи учетных данных работает на устройствах под управлением Android 8 (уровень API 26) и более поздних версий.
Добавить зависимости
Добавьте зависимость androidx.credentials:providerevents в build.gradle или build.gradle.kts вашего модуля:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Создайте экземпляр необходимого класса.
Создайте экземпляр необходимого объекта ProviderEventsManager .
val providerEventsManager = ProviderEventsManager.create(context)
Внедрить экспортер
Чтобы разрешить пользователям экспортировать учетные данные из вашего приложения в другие поставщики учетных данных на устройстве, реализуйте роль экспортера.
Регистрация экспортных заявок
При изменении поставщика учетных данных (например, при входе пользователя в систему, добавлении учетных данных или изменении учетных записей) зарегистрируйте или обновите элементы ExportEntry используя ProviderEventsManager.registerExport() .
Для каждой ExportEntry требуется:
-
id: Секретный, случайно сгенерированный строковый идентификатор, однозначно представляющий эту запись экспорта. Необходимо надежно сохранить этот идентификатор ; он понадобится позже для проверки входящих запросов на передачу. -
accountDisplayName: Необязательная метка учетной записи (например,"Personal Account"). -
userDisplayName: Основной идентификатор пользователя (например,"alice@example.com"). -
icon:Bitmapзначок, представляющий поставщика услуг или учетную запись (автоматически масштабируется библиотекой до размера 32x32 PNG). -
supportedCredentialTypes: Набор строковых констант изCredentialTypesуказывающих на типы данных, к которым относится эта запись.
suspend fun registerMyProviderForExport( providerEventsManager: ProviderEventsManager, providerIcon: Bitmap, // Randomly generated and stored in encrypted storage secretEntryId: String ) { val entry = ExportEntry( id = secretEntryId, accountDisplayName = "MyProvider Personal", userDisplayName = "alice@example.com", icon = providerIcon, supportedCredentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD ) ) // RegisterExportRequest.create() attaches the default WASM matcher from assets val request = RegisterExportRequest.create(context, listOf(entry)) try { val response = providerEventsManager.registerExport(request) // Registration successful } catch (e: Exception) { // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException) } }
Укажите действие экспортера в файле манифеста.
Когда пользователь выбирает ваш ExportEntry в пользовательском интерфейсе выбора системы, система запускает назначенное вами Activity для обработки запроса. Объявите это Activity с обязательными параметрами Intent, Action и Content URI scheme:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:label="@string/export_activity_label">
<intent-filter>
// This intent action is required for Credential Manager to invoke this activity
<action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
</intent-filter>
</activity>
Обработайте намерение передачи в вашем действии.
В методе CredentialExportActivity используйте IntentHandler.retrieveProviderImportCredentialsRequest(intent) чтобы проанализировать запрос, проверить вызывающее приложение и credId , выполнить необходимую биометрическую аутентификацию и записать полезную нагрузку FIDO CXF обратно в предоставленный URI содержимого:
class CredentialExportActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. Extract the transfer request from the incoming Intent val request: ProviderImportCredentialsRequest? = IntentHandler.retrieveProviderImportCredentialsRequest(intent) if (request == null) { finishWithError() return } // 2. Validate CallingAppInfo and secret `credId` val callingAppPackage = request.callingAppInfo.packageName val receivedCredId = request.credId if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) { // Secret ID mismatch or untrusted caller -> abort sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request")) return } // 3. Optional: Prompt user for Biometric / PIN authentication before exporting authenticateUserThenExport(request) } private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) { // ... Biometric prompt logic ... // Once authenticated, generate the FIDO CXF JSON string matching the requested types val cxfJsonPayload = buildFidoCxfJsonPayload( requestedTypes = request.request.credentialTypes, requestedExtensions = request.request.knownExtensions ) val response = ImportCredentialsResponse(cxfJsonPayload) // 4. Write the JSON payload to the Content URI and set Activity result IntentHandler.setImportCredentialsResponse( context = this, uri = request.uri, intent = intent, response = response ) setResult(Activity.RESULT_OK, intent) finish() } private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) { IntentHandler.setImportCredentialsException(intent, exception) setResult(Activity.RESULT_OK, intent) finish() } private fun verifySecretEntryId(credentialId: String): Boolean { // Check if credentialId matches what you stored when calling RegisterExportRequest return credentialId == getStoredSecretEntryId() } private fun isTrustedImporter(packageName: String): Boolean { // Implement any specific allowlisting / caller checks if required return true } private fun finishWithError() { setResult(Activity.RESULT_CANCELED) finish() } }
Исключения при регистрации и таможенном оформлении экспорта
Все исключения в этом модуле являются подклассами ImportCredentialsException , RegisterExportException или ClearExportException .
-
RegisterExportProviderConfigurationExceptionилиClearExportProviderConfigurationException: выбрасываются при сбое регистрации или очистки из-за проблем с настройкой поставщика (например, пустые значенияsupportedCredentialTypesвExportEntryили вызов неподдерживаемого уровня ОС). -
RegisterExportUnknownErrorExceptionилиClearExportUnknownErrorException: При обновлении реестра экспорта произошла неклассифицированная системная или ошибка хранения данных.
Реализуйте импортер.
Для импорта учетных данных в ваше приложение (например, во время регистрации или импорта данных поставщика) реализуйте роль импортера и запустите процесс, вызвав метод ProviderEventsManager .importCredentials() .
Сформируйте запрос на импорт и запустите процесс.
Укажите, какие CredentialTypes и KnownExtensions поддерживает ваш импортер:
suspend fun startCredentialImport( activityContext: Context, providerEventsManager: ProviderEventsManager ) { val importRequest = ImportCredentialsRequest( credentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_NOTE ), knownExtensions = setOf( KnownExtensions.KNOWN_EXTENSION_SHARED ) ) try { // Launches the system Selector UI; suspends until user selects a provider and completes transfer val response = providerEventsManager.importCredentials(activityContext, importRequest) // 1. Inspect the source exporter's package info val exporterPackageName = response.callingAppInfo.packageName // 2. Parse the FIDO CXF JSON string val cxfJsonString = response.response.responseJson parseAndSaveImportedCredentials(cxfJsonString) } catch (e: ImportCredentialsException) { // Handle specific import exceptions (e.g., ImportCredentialsCancellationException) handleImportFailure(e) } } private fun parseAndSaveImportedCredentials(cxfJsonString: String) { val rootJson = JSONObject(cxfJsonString) // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification: // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html } // Helper function to make it compile private fun handleImportFailure(e: ImportCredentialsException) {}
Исключения потока импорта
Все исключения в этом модуле являются подклассами ImportCredentialsException , RegisterExportException или ClearExportException .
-
ImportCredentialsCancellationException: Пользователь закрыл интерфейс выбора или отменил операцию экспорта (Activity.RESULT_CANCELED). -
ImportCredentialsNoExportOptionException: Ни одна зарегистрированная запись экспорта не соответствует запрошенным типам учетных данных, или выбранный экспортер выдал исключение об отклонении. -
ImportCredentialsProviderConfigurationException: Ошибка конфигурации (например, пустые значенияcredentialTypesзаданные вImportCredentialsRequest, или отсутствующие разрешения). -
ImportCredentialsInvalidJsonException: Экспортер вернул некорректный или пустой JSON-объект, проверка запроса на который не удалась. -
ImportCredentialsSystemErrorException: Во время импорта произошла внутренняя ошибка системы Android или ошибка передачи Binder. -
ImportCredentialsUnknownCallerException: Платформа не смогла проверить вызывающее приложение. -
ImportCredentialsUnknownErrorException: Неклассифицированная или непредвиденная ошибка в процессе импорта.
Поддерживаемые типы и расширения учетных данных
Объект androidx.credentials.providerevents.transfer.CredentialTypes определяет стандартизированные строковые константы, соответствующие типам элементов FIDO CXF:
| Постоянный | Значение (тип cxf ) | Описание |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH | "basic-auth" | Имя пользователя и пароль для входа в систему. |
CREDENTIAL_TYPE_PUBLIC_KEY | "passkey" | Учетные данные открытого ключа FIDO2 или WebAuthn. |
CREDENTIAL_TYPE_ADDRESS | "address" | Почтовый или транспортный адрес для автоматического заполнения формы. |
CREDENTIAL_TYPE_API_KEY | "api-key" | Ключи и токены доступа к API. |
CREDENTIAL_TYPE_CREDIT_CARD | "credit-card" | Информация для оплаты кредитными и дебетовыми картами. |
CREDENTIAL_TYPE_CUSTOM_FIELDS | "custom-fields" | Пользовательские группировки или определяемые пользователем поля. |
CREDENTIAL_TYPE_DRIVERS_LICENSE | "drivers-license" | Данные водительского удостоверения. |
CREDENTIAL_TYPE_FILE | "file" | Метаданные и ссылки-заполнители для бинарных файлов. |
CREDENTIAL_TYPE_GENERATED_PASSWORD | "generated-password" | Надежные пароли, генерируемые машинным способом. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT | "identity-document" | Национальные удостоверения личности, номер социального страхования (SSN), идентификационный номер налогоплательщика (TIN) или данные паспорта. |
CREDENTIAL_TYPE_ITEM_REFERENCE | "item-reference" | Логические ссылки, указывающие на другой элемент в полезной нагрузке. |
CREDENTIAL_TYPE_NOTE | "note" | Пользовательские защищенные заметки (строка UTF-8). |
CREDENTIAL_TYPE_PASSPORT | "passport" | Информация о паспорте и проездном документе. |
CREDENTIAL_TYPE_PERSON_NAME | "person-name" | Личные данные и имя человека. |
CREDENTIAL_TYPE_SSH_KEY | "ssh-key" | Пары открытого и закрытого ключей SSH. |
CREDENTIAL_TYPE_TOTP | "totp" | Секреты одноразовых паролей (2FA), основанные на времени. |
CREDENTIAL_TYPE_WIFI | "wifi" | Идентификатор сети Wi-Fi (SSID) и пароль. |
Пользовательские сопоставители WASM (WebAssembly) (расширенные настройки)
По умолчанию вызов RegisterExportRequest.create(context, entries) включает в состав библиотеки файл credential_transfer_matcher.wasm , который фильтрует записи исключительно на основе пересечения значений supportedCredentialTypes .
Если ваш поставщик учетных данных требует сложной логики сопоставления (например, динамических проверок возможностей или условной фильтрации на основе пользовательских полей), вы можете скомпилировать собственный модуль WebAssembly, соответствующий API передачи учетных данных WASM, и передавать массив байтов напрямую:
// Loading a custom WASM matcher val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() } val customRequest = RegisterExportRequest( entries = myEntries, exportMatcher = customMatcherBytes ) providerEventsManager.registerExport(customRequest)