Die APIs für die Übertragung von Anmeldedaten des Credential Manager ermöglichen die sichere Übertragung von Anmeldedaten von Nutzern zwischen Anmeldedatenanbietern auf demselben Gerät. In dieser Anleitung wird beschrieben, wie Anmeldedatenanbieter auf Android in die APIs der androidx.credentials:providerevents-Bibliothek integriert werden können. Diese Funktion unterstützt Passwörter, Passkeys, Adressinformationen und benutzerdefinierte Felder im standardisierten FIDO Credential Exchange Format (CXF).
Wichtige Konzepte
Das Framework für die Übertragung von Anmeldedaten ermöglicht die Peer-to-Peer-Übertragung von Anmeldedaten auf demselben Gerät, ohne dass Rohanmeldedaten für das Android-Betriebssystem oder nicht authentifizierte Apps offengelegt werden.
Das Framework definiert zwei primäre Rollen:
- Exporter (Quellanbieter): Ein Anmeldedatenanbieter, der derzeit Nutzeranmeldedaten enthält. Es registriert Metadaten zu verfügbaren exportierbaren Konten (
ExportEntry) vorab im System und reagiert auf Übertragungsanfragen, wenn der Nutzer sie auswählt. - Importer (Clientanbieter oder Einrichtungsassistent): Ein Anmeldedatenanbieter oder Einrichtungsassistent, der eine Importanfrage (
ImportCredentialsRequest) initiiert und angibt, welche Arten von Anmeldedaten und Erweiterungen er empfangen kann.
Kompatibilität mit Android-Versionen
Die Credentials Transfer API funktioniert auf Geräten mit Android 8 (API-Level 26) und höher.
Abhängigkeiten hinzufügen
Fügen Sie die androidx.credentials:providerevents-Abhängigkeit der build.gradle- oder build.gradle.kts-Datei Ihres Moduls hinzu:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
Erforderliche Klasse instanziieren
Erstellen Sie eine Instanz der erforderlichen ProviderEventsManager.
val providerEventsManager = ProviderEventsManager.create(context)
Exporter implementieren
Wenn Sie Nutzern ermöglichen möchten, Anmeldedaten aus Ihrer App in andere Anmeldedatenanbieter auf dem Gerät zu exportieren, implementieren Sie die Rolle „Exporter“.
Export-Einträge registrieren
Wenn sich Ihr Anmeldedatenanbieter ändert (z. B. wenn sich ein Nutzer anmeldet, Anmeldedaten hinzufügt oder Konten ändert), registrieren oder aktualisieren Sie Ihre ExportEntry-Elemente mit ProviderEventsManager.registerExport().
Für jede ExportEntry ist Folgendes erforderlich:
id: Eine geheime, zufällig generierte String-Kennung, die diesen Exporteintrag eindeutig repräsentiert. Sie müssen diese ID sicher speichern, da Sie sie später zum Bestätigen eingehender Übertragungsanfragen benötigen.accountDisplayName: Optionales Kontolabel (z. B."Personal Account").userDisplayName: Die primäre Kennung des Nutzers (z. B."alice@example.com").icon: EinBitmap-Symbol, das den Anbieter oder das Konto darstellt (wird von der Bibliothek automatisch auf 32 × 32 PNG skaliert).supportedCredentialTypes: Eine Reihe von Stringkonstanten ausCredentialTypes, die die Typen dieses Eintrags darstellen.
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) } }
Exporter-Aktivität in der Manifestdatei deklarieren
Wenn der Nutzer Ihre ExportEntry in der Systemauswahl-UI auswählt, startet das System die dafür vorgesehene Activity. Deklarieren Sie diese Aktivität mit der obligatorischen Intent-Aktion und dem Inhalts-URI-Schema.
Um sich vor unautorisierten Aufrufen und Confused-Deputy-Angriffen zu schützen, deklarieren Sie android:requireContentUriPermissionFromCaller="write" unter Android 14 (API-Level 34) und höher. Dadurch wird das Android-Betriebssystem angewiesen, zu prüfen, ob der Aufrufer, der den Start initiiert, Schreibberechtigungen für den URI des Zielinhalts hat, bevor die Aktivität gestartet werden darf. Das folgende Beispiel zeigt, wie das funktioniert:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:requireContentUriPermissionFromCaller="write"
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>
Transfer-Intent in Ihrer Aktivität verarbeiten
Verwenden Sie in Ihrem CredentialExportActivity IntentHandler.retrieveProviderImportCredentialsRequest(intent), um die Anfrage zu parsen, die aufrufende Komponente und die Metadaten der Anfrage zu prüfen, die erforderliche biometrische Authentifizierung durchzuführen und die FIDO CXF-Nutzlast zurück in den bereitgestellten Inhalts-URI zu schreiben.
Anfrage authentifizieren
Da Ihre Aktivitäten exportiert werden, müssen Sie den Export von Anmeldedaten schützen, indem Sie jede der folgenden Ebenen überprüfen:
credId-Sitzungs-Nonce bestätigen (primäre Schutzmaßnahme): Wenn Sie IhreExportEntryregistrieren, generieren Sie eine kryptografisch zufälligeid(Nonce) mit hoher Entropie und verknüpfen Sie sie mit einer kurzlebigen Sitzung. Prüfen Sie in Ihrer Aktivität, obrequest.credIdmit einer aktiven, nicht abgelaufenen Sitzungs-Nonce übereinstimmt, die zuvor von Ihrem Anbieter ausgestellt wurde. Da Drittanbieter-Apps diese Nonce nicht erraten können, wird sichergestellt, dass die Absicht einer authentischen Nutzerauswahl in der Systemauswahl entspricht.Activity.getCallingPackage()bestätigen (Anruferprüfung): Da der Anmeldedaten-Selektor Ihre Aktivität mitstartActivityForResultstartet, kann Ihre AktivitätgetCallingPackage()prüfen, um zu bestätigen, dass der unmittelbare Aufrufer, der die Benutzeroberfläche startet, das vertrauenswürdige System-Framework für den Anmeldedaten-Selektor ist (auf Android-Geräten mit Google Play-Diensten"com.google.android.gms"). Lehnen Sie die Anfrage ab, wenn das aufrufende Paket nicht erkannt wird.request.callingAppInfoprüfen (Informationen zur Ziel-App): MitcallingAppInfowird die Zielanwendung identifiziert, die die importierten Anmeldedaten empfängt. Sie können diese Informationen nach Belieben für die Transparenz verwenden, z. B. um den Namen und das Symbol der Ziel-App in Ihrer Bestätigungs-UI anzuzeigen oder um zu überprüfen, ob die Zielanwendung vertrauenswürdig ist.
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() } }
Registrierungs- und Clearing-Ausnahmen exportieren
Alle Ausnahmen in diesem Modul sind Unterklassen von ImportCredentialsException, RegisterExportException oder ClearExportException.
RegisterExportProviderConfigurationExceptionoderClearExportProviderConfigurationException: Wird ausgelöst, wenn die Registrierung oder das Löschen aufgrund von Problemen bei der Einrichtung des Anbieters fehlschlägt (z. B. leerersupportedCredentialTypesin einemExportEntryoder Aufruf auf einer nicht unterstützten Betriebssystemebene).RegisterExportUnknownErrorExceptionoderClearExportUnknownErrorException: Beim Aktualisieren der Exportregistrierung ist ein nicht klassifizierter System- oder Speicherfehler aufgetreten.
Importer implementieren
Wenn Sie Anmeldedaten in Ihre App importieren möchten (z. B. während des Onboardings oder des Anbieterimports), implementieren Sie die Rolle „Importer“ und starten Sie den Ablauf durch Aufrufen von ProviderEventsManager.importCredentials().
Importanfrage und Launch-Ablauf erstellen
Geben Sie an, welche CredentialTypes und KnownExtensions von Ihrem Importtool unterstützt werden:
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) {}
Ausnahmen beim Importieren von Abläufen
Alle Ausnahmen in diesem Modul sind Unterklassen von ImportCredentialsException, RegisterExportException oder ClearExportException.
ImportCredentialsCancellationException: Der Nutzer hat die Auswahl-UI geschlossen oder den Exportvorgang abgebrochen (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: Es wurden keine registrierten Exporteinträge gefunden, die den angeforderten Anmeldedatentypen entsprechen, oder der ausgewählte Exporteur hat eine Ablehnungs-Ausnahme ausgelöst.ImportCredentialsProviderConfigurationException: Konfigurationsfehler (z. B. leerecredentialTypesinImportCredentialsRequestoder fehlende Berechtigungen).ImportCredentialsInvalidJsonException: Der Exporteur hat eine fehlerhafte oder leere JSON-Nutzlast zurückgegeben, die die Anfragevalidierung nicht bestanden hat.ImportCredentialsSystemErrorException: Beim Import ist ein interner Android-Systemfehler oder ein Binder-Übertragungsfehler aufgetreten.ImportCredentialsUnknownCallerException: Die aufrufende App konnte vom Framework nicht überprüft werden.ImportCredentialsUnknownErrorException: Nicht klassifizierter oder unerwarteter Fehler während des Importvorgangs.
Unterstützte Anmeldedatentypen und ‑erweiterungen
Das androidx.credentials.providerevents.transfer.CredentialTypes-Objekt definiert standardisierte Stringkonstanten, die den FIDO-CXF-Elementtypen entsprechen:
| Konstante | Wert (cxf-Typ) |
Beschreibung |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
Anmeldedaten mit Nutzername und Passwort. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
Öffentliche Schlüsselanmeldedaten für FIDO2- oder WebAuthn-Passkeys. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
Informationen zur Post- oder Versandadresse für das automatische Ausfüllen von Formularen. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
API-Zugriffsschlüssel und ‑tokens. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
Zahlungsinformationen für Kredit- und Debitkarten. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
Benutzerdefinierte Gruppierungen oder benutzerdefinierte Felder. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
Führerscheindetails. |
CREDENTIAL_TYPE_FILE |
"file" |
Metadaten und Platzhalterreferenzen für Binärdateien. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
Maschinell generierte sichere Passwörter |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
Personalausweise, Sozialversicherungsnummern, Steuernummern oder Reisepassnummern. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
Logische Links, die auf ein anderes Element in der Nutzlast verweisen. |
CREDENTIAL_TYPE_NOTE |
"note" |
Benutzerdefinierte vertrauliche Notizen (UTF-8-String). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
Reisepassdaten |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
Identität und Namensdetails der Person. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
Öffentliche und private SSH-Schlüsselpaare. |
CREDENTIAL_TYPE_TOTP |
"totp" |
Zeitbasierte Einmalpasswort-Secrets (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
SSID und Passphrasen des WLANs. |
Benutzerdefinierte WASM-Matcher (WebAssembly) (erweitert)
Standardmäßig wird beim Aufrufen von RegisterExportRequest.create(context, entries) die systemstandardmäßige credential_transfer_matcher.wasm aus Bibliotheks-Assets gebündelt. Einträge werden also nur auf Grundlage der Überschneidung von supportedCredentialTypes gefiltert.
Wenn Ihr Anmeldedatenanbieter eine komplexe Abgleichslogik erfordert (z. B. dynamische Funktionsprüfungen oder bedingtes Filtern basierend auf benutzerdefinierten Feldern), können Sie ein eigenes WebAssembly-Modul kompilieren, das der WASM-API für die Übertragung von Anmeldedaten entspricht, und das Rohbyte-Array direkt übergeben:
// 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)