Interfejsy API do przenoszenia danych logowania w Menedżerze danych logowania umożliwiają bezpieczne przenoszenie danych logowania użytkownika między dostawcami danych logowania na tym samym urządzeniu. Z tego przewodnika dowiesz się
jak dostawcy danych logowania na Androidzie mogą integrować się z interfejsami API udostępnianymi przez
androidx.credentials:providerevents bibliotekę. Ta funkcja obsługuje
hasła, klucze dostępu, informacje o adresie i pola niestandardowe za pomocą
standardowego formatu wymiany danych logowania FIDO (CXF).
Podstawowe pojęcia
Platforma przenoszenia danych logowania ułatwia przenoszenie danych logowania między urządzeniami na tym samym urządzeniu bez ujawniania surowych danych logowania systemowi Android ani nieuwierzytelnionym aplikacjom.
Platforma definiuje 2 główne role:
- Eksporter (dostawca źródłowy): dostawca danych logowania, który obecnie przechowuje dane logowania użytkownika. Wstępnie rejestruje w systemie metadane dotyczące dostępnych kont, które można wyeksportować (
ExportEntry), i odpowiada na żądania przeniesienia , gdy użytkownik je wybierze. - Importer (dostawca klienta lub kreator konfiguracji): dostawca danych logowania lub
kreator konfiguracji, który inicjuje żądanie importu
(
ImportCredentialsRequest), określając, jakie typy danych logowania i rozszerzeń może odbierać.
Zgodność z wersją Androida
Interfejs Credentials Transfer API działa na urządzeniach z Androidem 8 (poziom API 26) lub nowszym.
Dodawanie zależności
Dodaj zależność androidx.credentials:providerevents do pliku
build.gradle lub build.gradle.kts modułu:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Tworzenie instancji wymaganej klasy
Utwórz instancję wymaganej klasy ProviderEventsManager.
val providerEventsManager = ProviderEventsManager.create(context)
Implementowanie eksportera
Aby umożliwić użytkownikom eksportowanie danych logowania z Twojej aplikacji do innych dostawców danych logowania na urządzeniu, zaimplementuj rolę eksportera.
Rejestrowanie wpisów eksportu
Gdy dostawca danych logowania się zmieni (np. użytkownik się zaloguje, doda dane logowania lub zmodyfikuje konta), zarejestruj lub zaktualizuj elementy ExportEntry za pomocą ProviderEventsManager.registerExport().
Każdy element ExportEntry wymaga:
id: tajny, losowo wygenerowany ciąg znaków, który jednoznacznie reprezentuje ten wpis eksportu. Musisz bezpiecznie przechowywać ten identyfikator ; będzie on potrzebny później do weryfikowania przychodzących żądań przeniesienia.accountDisplayName: opcjonalna etykieta konta (np."Personal Account").userDisplayName: główny identyfikator użytkownika (np."alice@example.com").icon: ikonaBitmapreprezentująca dostawcę lub konto (biblioteka automatycznie skaluje ją do formatu PNG o wymiarach 32 x 32).supportedCredentialTypes: zbiór stałych ciągów znaków zCredentialTypesreprezentujących typy danych logowania, które zawiera ten wpis.
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) } }
Deklarowanie aktywności eksportera w pliku manifestu
Gdy użytkownik wybierze Twój element ExportEntry w interfejsie selektora systemu, system uruchomi wyznaczoną Activity obsługi. Zadeklaruj tę aktywność za pomocą obowiązkowej akcji intencji i schematu identyfikatora URI treści:
<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>
Obsługa intencji przeniesienia w aktywności
W CredentialExportActivity użyj IntentHandler.retrieveProviderImportCredentialsRequest(intent), aby przeanalizować żądanie, zweryfikować aplikację wywołującą i credId, przeprowadzić wymaganą uwierzytelnianie biometryczne i zapisać ładunek FIDO CXF z powrotem do podanego identyfikatora URI treści:
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() } }
Wyjątki rejestracji eksportu i czyszczenia
Wszystkie wyjątki w tym module są podklasami
ImportCredentialsException, RegisterExportException, or
ClearExportException.
RegisterExportProviderConfigurationExceptionlubClearExportProviderConfigurationException: zgłaszany, gdy rejestracja lub czyszczenie nie powiedzie się z powodu problemów z konfiguracją dostawcy (np. pusta wartośćsupportedCredentialTypeswExportEntry, lub wywołanie w nieobsługiwanej warstwie systemu operacyjnego).RegisterExportUnknownErrorExceptionlubClearExportUnknownErrorException: podczas aktualizowania rejestru eksportu wystąpił niezaklasyfikowany błąd systemu lub pamięci.
Implementowanie importera
Aby importować dane logowania do aplikacji (np. podczas wdrażania lub importowania dostawcy
), zaimplementuj rolę importera i zainicjuj proces, wywołując
ProviderEventsManager.importCredentials().
Tworzenie żądania importu i uruchamianie procesu
Określ, które CredentialTypes i KnownExtensions są obsługiwane przez importera:
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) {}
Wyjątki procesu importu
Wszystkie wyjątki w tym module są podklasami
ImportCredentialsException, RegisterExportException, or
ClearExportException.
ImportCredentialsCancellationException: użytkownik zamknął interfejs selektora lub anulował aktywność eksportu (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: żadne zarejestrowane wpisy eksportu nie pasowały do żądanych typów danych logowania lub wybrany eksporter zgłosił wyjątek odrzucenia.ImportCredentialsProviderConfigurationException: błąd konfiguracji (np. pusty zbizacredentialTypeswImportCredentialsRequestlub brak uprawnień).ImportCredentialsInvalidJsonException: eksporter zwrócił nieprawidłowy lub pusty ładunek JSON, który nie przeszedł weryfikacji żądania.ImportCredentialsSystemErrorException: podczas importowania wystąpił wewnętrzny błąd systemu Android lub błąd przenoszenia Binder.ImportCredentialsUnknownCallerException: nie udało się zweryfikować aplikacji wywołującej przez platformę.ImportCredentialsUnknownErrorException: niezaklasyfikowany lub nieoczekiwany błąd podczas procesu importu.
Obsługiwane typy danych logowania i rozszerzenia
Obiekt androidx.credentials.providerevents.transfer.CredentialTypes definiuje standardowe stałe ciągi znaków odpowiadające typom elementów FIDO CXF:
| Stała | Wartość (typ cxf) |
Opis |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Dane logowania z nazwą użytkownika i hasłem. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Dane logowania z kluczem publicznym FIDO2 lub WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Informacje o adresie pocztowym lub dostawy do automatycznego wypełniania formularzy. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
Klucze i tokeny dostępu do interfejsu API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Informacje o płatnościach kartą kredytową lub debetową. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Niestandardowe grupowania lub pola zdefiniowane przez użytkownika. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Szczegóły prawa jazdy. |
CREDENTIAL_TYPE_FILE |
"file" |
Metadane i odniesienia do plików binarnych. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Bezpieczne hasła generowane przez maszynę. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Dowody osobiste, numery SSN, numery TIN lub paszporty. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Logiczne linki wskazujące inny element w ładunku. |
CREDENTIAL_TYPE_NOTE |
"note" |
Bezpieczne notatki zdefiniowane przez użytkownika (ciąg znaków UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Szczegóły dokumentu podróży – paszportu. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Dane osobowe i informacje o nazwie. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Pary kluczy publicznych i prywatnych SSH. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Tajne kody jednorazowe oparte na czasie (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
Identyfikator SSID i hasła sieci Wi-Fi. |
Niestandardowe dopasowania WASM (WebAssembly) (zaawansowane)
Domyślnie wywołanie RegisterExportRequest.create(context, entries) powoduje powiązanie domyślnego dla systemu pliku credential_transfer_matcher.wasm z zasobów biblioteki, który filtruje wpisy wyłącznie na podstawie przecięcia supportedCredentialTypes.
Jeśli dostawca danych logowania wymaga złożonej logiki dopasowywania (np. dynamicznych kontroli możliwości lub filtrowania warunkowego na podstawie pól niestandardowych), możesz skompilować własny moduł WebAssembly zgodny z interfejsem API przenoszenia danych logowania WASM i przekazać bezpośrednio surową tablicę bajtów:
// 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)