Передача учетных данных

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 .

Реализуйте импортер.

Для импорта учетных данных в ваше приложение (например, во время регистрации или импорта данных поставщика) реализуйте роль импортера и запустите процесс, вызвав метод 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 .

Поддерживаемые типы и расширения учетных данных

Объект 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)