Der Anmeldedaten-Manager bezieht sich auf eine Reihe von APIs, die in Android 14 eingeführt wurden und mehrere Anmeldemethoden wie Nutzername/Passwort, Passkeys und Lösungen für die föderierte Anmeldung (z. B. „Über Google anmelden“) unterstützen. Wenn die Credential Manager API aufgerufen wird, aggregiert das Android-System Anmeldedaten von allen auf dem Gerät installierten Anmeldedatenanbietern. In diesem Dokument werden die APIs beschrieben, die Integrationsendpunkte für diese Anbieter von Anmeldedaten bereitstellen.
Einrichten
Bevor Sie die Funktionen bei Ihrem Anmeldedatenanbieter implementieren, führen Sie die in den folgenden Abschnitten beschriebenen Einrichtungsschritte aus.
Abhängigkeiten deklarieren
Deklarieren Sie in der Datei build.gradle
Ihres Moduls eine Abhängigkeit mithilfe der neuesten Version der Bibliothek des Anmeldedaten-Managers:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Dienstelement in Manifestdatei deklarieren
Füge in der Manifestdatei AndroidManifest.xml
deiner App eine <service>
-Deklaration für eine Dienstklasse ein, die die Klasse CredentialProviderService
aus der androidx.credentials-Bibliothek erweitert, wie im folgenden Beispiel gezeigt.
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="<any drawable icon>"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
Die oben gezeigten Berechtigungen und der Intent-Filter sind entscheidend dafür, dass der Anmeldedaten-Manager wie erwartet funktioniert. Die Berechtigung ist erforderlich, damit nur das Android-System eine Verbindung zu diesem Dienst herstellen kann. Der Intent-Filter wird zur Auffindbarkeit dieses Dienstes als Anmeldedatenanbieter verwendet, der vom Anmeldedaten-Manager verwendet werden kann.
Unterstützte Anmeldedatentypen deklarieren
Erstellen Sie in Ihrem res/xml
-Verzeichnis eine neue Datei mit dem Namen provider.xml
. Deklarieren Sie in dieser Datei die von Ihrem Dienst unterstützten Anmeldedatentypen durch Konstanten, die für jeden Berechtigungstyp in der Bibliothek definiert sind. Im folgenden Beispiel unterstützt der Dienst sowohl traditionelle Passwörter als auch Passkeys, für die Konstanten als TYPE_PASSWORD_CREDENTIAL
und TYPE_PUBLIC_KEY_CREDENTIAL
definiert sind:
<?xml version="1.0" encoding="utf-8"?>
<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>
Auf früheren API-Ebenen integrieren Anbieter von Anmeldedaten APIs wie das automatische Ausfüllen für Passwörter und andere Daten. Diese Anbieter können dieselbe interne Infrastruktur zum Speichern der vorhandenen Anmeldedatentypen verwenden und sie gleichzeitig für andere Typen, einschließlich Passkeys, ausweiten.
Zweiphasiger Ansatz zur Interaktion mit Dienstleistern
Die Interaktion zwischen dem Anmeldedaten-Manager und den Anbietern von Anmeldedaten erfolgt in zwei Phasen:
- Die erste Phase ist die Beginn/Abfragephase, in der sich das System an Dienste des Anmeldedatenanbieters bindet und die Methoden
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
oderonClearCredentialStateRequest()
mit Anfragen vom TypBegin…
aufruft. Anbieter müssen diese Anfragen verarbeiten und mitBegin…
-Antworten als Antwort mit Einträgen versehen, die visuelle Optionen darstellen, die in der Kontoauswahl angezeigt werden sollen. Für jeden Eintrag muss einPendingIntent
festgelegt sein. - Sobald der Nutzer einen Eintrag auswählt, beginnt die Auswahlphase. Die mit dem Eintrag verknüpfte
PendingIntent
wird ausgelöst, um die entsprechende Anbieteraktivität aufzurufen. Nachdem der Nutzer mit dieser Aktivität interagiert hat, muss der Anbieter von Anmeldedaten die Antwort auf das Ergebnis der Aktivität festlegen, bevor er sie beendet. Diese Antwort wird dann an die Clientanwendung gesendet, die den Anmeldedaten-Manager aufgerufen hat.
Passkey-Erstellung verarbeiten
Abfragen zum Erstellen von Passkeys verarbeiten
Wenn eine Client-App einen Passkey erstellen und bei einem Anmeldedatenanbieter speichern möchte, ruft sie die createCredential
API auf. Führe die Schritte in den folgenden Abschnitten aus, um diese Anfrage in deinem Dienst für Anmeldedatenanbieter so zu verarbeiten, dass der Passkey tatsächlich in deinem Speicher gespeichert wird.
- Überschreiben Sie die Methode
onBeginCreateCredentialRequest()
in Ihrem Dienst, der vonCredentialProviderService
erweitert wurde. - Erstellen Sie zur Verarbeitung des
BeginCreateCredentialRequest
ein entsprechendesBeginCreateCredentialResponse
-Objekt und übergeben Sie es über den Callback. - Fügen Sie beim Erstellen des
BeginCreateCredentialResponse
die erforderlichenCreateEntries
hinzu. JedesCreateEntry
sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und es muss einPendingIntent
-Objekt sowie andere erforderliche Metadaten festgelegt sein.
Das folgende Beispiel veranschaulicht, wie diese Schritte implementiert werden.
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
)
)
}
Die Konstruktion von PendingIntent
sollte den folgenden Anforderungen entsprechen:
- Die entsprechende Aktivität sollte so eingerichtet werden, dass alle erforderlichen biometrischen Aufforderungen, Bestätigungen oder Auswahlen angezeigt werden.
- Alle erforderlichen Daten, die der Anbieter benötigt, wenn die entsprechende Aktivität aufgerufen wird, sollten als Extra für den Intent festgelegt werden, der zum Erstellen der
PendingIntent
verwendet wird, z. B. einaccountId
im Erstellungsablauf. - Ihre
PendingIntent
muss mit dem FlagPendingIntent.FLAG_MUTABLE
erstellt werden, damit das System die letzte Anfrage an das zusätzliche Intent-Element anhängen kann. - Ihr
PendingIntent
darf nicht mit dem FlagPendingIntent.FLAG_ONE_SHOT
erstellt werden, da der Nutzer einen Eintrag auswählen kann. Dann kann er zurückgehen und ihn noch einmal auswählen. Das würde dazu führen, dass dasPendingIntent
zweimal ausgelöst wird. - Ihre
PendingIntent
muss mit einem eindeutigen Anfragecode erstellt werden, damit jeder Eintrag eine eigene entsprechendePendingIntent
haben kann.
Auswahl von Einträgen für Anfragen zur Passkey-Erstellung verarbeiten
- Wenn der Nutzer eine zuvor ausgefüllte
CreateEntry
auswählt, wird die entsprechendePendingIntent
aufgerufen und der zugehörige AnbieterActivity
erstellt. - Nachdem die Methode
onCreate
Ihrer Aktivität aufgerufen wurde, greifen Sie auf den zugehörigen Intent zu und übergeben Sie ihn an die KlassePendingIntentHander
, um denProviderCreateCredentialRequest
abzurufen. - Extrahieren Sie
requestJson
,callingAppInfo
undclientDataHash
aus der Anfrage. - Extrahieren Sie das lokale
accountId
aus dem Intent-Extra. Dies ist eine Beispielimplementierung für eine Anwendung, die nicht erforderlich ist. Mit dieser Konto-ID können die Anmeldedaten für diese Konto-ID gespeichert werden. - Prüfen Sie die
requestJson
. Im folgenden Beispiel werden lokale Datenklassen wiePublicKeyCredentialCreationOptions
verwendet, um die JSON-Eingabe gemäß der WebAuthn-Spezifikation in eine strukturierte Klasse zu konvertieren. Als Anmeldedatenanbieter können Sie diese durch Ihren eigenen Parser ersetzen. - Prüfe den Asset-Link für die aufrufende App, wenn der Aufruf von einer nativen Android-App stammt.
- Authentifizierungsaufforderung anzeigen. Im folgenden Beispiel wird die Biometric API von Android verwendet.
- Wenn die Authentifizierung erfolgreich ist, generieren Sie ein
credentialId
und ein Schlüsselpaar. - Speichern Sie den privaten Schlüssel in Ihrer lokalen Datenbank für
callingAppInfo.packageName
. - Erstelle eine JSON-Antwort für die Web Authentication API, die aus dem öffentlichen Schlüssel und dem
credentialId
besteht. Im folgenden Beispiel werden lokale Dienstprogrammklassen wieAuthenticatorAttestationResponse
undFidoPublicKeyCredential
verwendet, die beim Erstellen einer JSON-Datei basierend auf der oben genannten Spezifikation helfen.Als Anmeldedatenanbieter können Sie diese Klassen durch Ihre eigenen Builder ersetzen. - Erstellen Sie ein
CreatePublicKeyCredentialResponse
mit dem oben generierten JSON. - Legen Sie
CreatePublicKeyCredentialResponse
als zusätzliches Element für einenIntent
bisPendingIntentHander.setCreateCredentialResponse()
fest und legen Sie diesen Intent auf das Ergebnis der Aktivität fest. - Beende die Aktivität.
Das Codebeispiel unten veranschaulicht diese Schritte. Dieser Code muss in der Activity-Klasse verarbeitet werden, nachdem onCreate()
aufgerufen wurde.
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
)
}
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
<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)
// 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
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result, createPublicKeyCredResponse
)
setResult(Activity.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)
}
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)}"
}
Abfragen für Anfragen zur Passworterstellung verarbeiten
So verarbeiten Sie Abfragen für Anfragen zur Passworterstellung:
- Fügen Sie in der im vorherigen Abschnitt erwähnten Methode
processCreateCredentialRequest()
innerhalb des Switch-Blocks einen weiteren Fall für die Verarbeitung von Passwortanfragen hinzu. - Fügen Sie beim Erstellen des
BeginCreateCredentialResponse
den erforderlichenCreateEntries
hinzu. - Jede
CreateEntry
sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und es muss einPendingIntent
sowie andere Metadaten dafür festgelegt sein.
Das folgende Beispiel zeigt, wie diese Schritte implementiert werden:
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
}
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)
}
Auswahl von Einträgen für Anfragen zur Passworterstellung verarbeiten
Wenn der Nutzer eine ausgefüllte CreateEntry
auswählt, wird der entsprechende PendingIntent
ausgeführt und die zugehörige Aktivität aufgerufen. Rufen Sie den zugehörigen Intent auf, der in onCreate
übergeben wurde, und übergeben Sie ihn an die Klasse PendingIntentHander
, um die Methode ProviderCreateCredentialRequest
abzurufen.
Das folgende Beispiel veranschaulicht, wie dieser Prozess implementiert wird. Dieser Code muss in der Methode onCreate()
Ihrer Aktivität verarbeitet werden.
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
<your_database>.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)
this@<activity>.finish()
Nutzeranmeldung
Die Nutzeranmeldung läuft so ab:
- Wenn eine Clientanwendung versucht, einen Nutzer anzumelden, wird eine
GetCredentialRequest
-Instanz vorbereitet. - Das Android-Framework leitet diese Anfrage durch eine Bindung an diese Dienste an alle anwendbaren Anmeldedatenanbieter weiter.
- Der Anbieterdienst empfängt dann ein
BeginGetCredentialRequest
mit einer Liste vonBeginGetCredentialOption
, von denen jeder Parameter enthält, mit denen übereinstimmende Anmeldedaten abgerufen werden können.
Führe die folgenden Schritte aus, um diese Anfrage in deinem Anmeldedaten-Anbieterdienst zu verarbeiten:
Überschreiben Sie die Methode
onBeginGetCredentialRequest()
, um die Anfrage zu verarbeiten. Wenn deine Anmeldedaten gesperrt sind, kannst du sofort einAuthenticationAction
für die Antwort festlegen und den Callback aufrufen.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()) } }
Anbieter, bei denen das Entsperren der Anmeldedaten vor dem Zurückgeben von
credentialEntries
erforderlich ist, müssen einen ausstehenden Intent einrichten, der den Nutzer zum Entsperrvorgang der App weiterleitet: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 ) ) }
Rufen Sie Anmeldedaten aus Ihrer lokalen Datenbank ab und richten Sie sie mit
CredentialEntries
ein, damit sie in der Auswahl angezeigt werden. Für Passkeys kannst ducredentialId
als zusätzliches Extra für den Intent festlegen, um zu ermitteln, welchen Anmeldedaten er zugeordnet ist, wenn der Nutzer diesen Eintrag auswählt.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 processGetCredentialsRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackage = request.callingAppInfo?.packageName val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackage, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackage, option ) ) ) } else -> { Log.i(TAG, "Request not supported") } } } return BeginGetCredentialResponse(credentialEntries) }
Fragen Sie Anmeldedaten aus Ihrer Datenbank ab und erstellen Sie Passkey- und Passworteinträge, die ausgefüllt werden sollen.
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 ), beginPublicKeyCredentialOption = 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) ) }
Nachdem Sie die Anmeldedaten abfragen und eingetragen haben, müssen Sie die Auswahlphase für die vom Nutzer ausgewählten Anmeldedaten ausführen, unabhängig davon, ob es sich um einen Passkey oder ein Passwort handelt.
Nutzerauswahl für Passkeys verarbeiten
- Rufen Sie in der Methode
onCreate
der entsprechenden Aktivität den zugehörigen Intent ab und übergeben Sie ihn anPendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Extrahieren Sie das
GetPublicKeyCredentialOption
aus der oben abgerufenen Anfrage. Extrahieren Sie anschließendrequestJson
undclientDataHash
aus dieser Option. - Extrahieren Sie das
credentialId
aus dem Intent-Extra, der vom Anmeldedatenanbieter beim Einrichten des entsprechendenPendingIntent
-Objekts eingefügt wurde. - Extrahieren Sie den Passkey mithilfe der oben genannten Anfrageparameter aus Ihrer lokalen Datenbank.
Sicherstellen, dass der Passkey mit extrahierten Metadaten und Nutzerüberprüfung gültig ist
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest.credentialOption as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo.getString("credId") // Get the saved passkey from your database based on the credential ID // from the publickeyRequest val passkey = <your database>.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 )
Lassen Sie den Nutzer über eine biometrische Aufforderung (oder eine andere Assertion-Methode) validieren. Im folgenden Code-Snippet wird die Android Biometric API verwendet.
Sobald die Authentifizierung erfolgreich ist, erstelle eine JSON-Antwort basierend auf der W3 Web Authentication Assertion-Spezifikation. Im folgenden Code-Snippet werden Hilfsdatenklassen wie
AuthenticatorAssertionResponse
verwendet, um strukturierte Parameter zu erfassen und in das erforderliche JSON-Format zu konvertieren. Die Antwort enthält eine digitale Signatur aus dem privaten Schlüssel einer WebAuthn-Anmeldedaten. Der Server der vertrauenden Partei kann diese Signatur verifizieren, um einen Nutzer vor der Anmeldung zu authentifizieren.Erstelle ein
PublicKeyCredential
mit dem oben generierten JSON und lege es auf ein finalesGetCredentialResponse
-Element fest. Legen Sie diese endgültige Antwort auf das Ergebnis dieser Aktivität fest.
Das folgende Beispiel zeigt, wie diese Schritte implementiert werden können:
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
<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
)
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)
Nutzerauswahl für die Passwortauthentifizierung verarbeiten
- Rufen Sie in der entsprechenden Aktivität den an
onCreate
übergebenen Intent auf und extrahieren SieProviderGetCredentialRequest
mithilfe vonPendingIntentHandler
. Verwenden Sie
GetPasswordOption
in der Anfrage, um die Anmeldedaten für den eingehenden Paketnamen abzurufen.val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption val username = passwordOption.username // Fetch the credentials for the calling app package name val creds = <your_database>.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext() == true) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }
Legen Sie nach dem Abrufen die Antwort für die ausgewählten Passwortanmeldedaten fest.
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
Auswahl eines Eintrags für die Authentifizierungsaktion verarbeiten
Wie bereits erwähnt kann ein Anmeldedatenanbieter einen AuthenticationAction
festlegen, wenn die Anmeldedaten gesperrt sind. Wenn der Nutzer diesen Eintrag auswählt, wird die Aktivität aufgerufen, die der Intent-Aktionsgruppe in PendingIntent
entspricht. Anmeldedatenanbieter können dann einen biometrischen Authentifizierungsvorgang oder einen ähnlichen Mechanismus zum Entsperren der Anmeldedaten verwenden. Bei Erfolg muss der Anmeldedatenanbieter ein BeginGetCredentialResponse
erstellen, ähnlich wie oben beschrieben, wie die Anmeldung von Nutzern beschrieben wird, da die Anmeldedaten jetzt entsperrt werden. Diese Antwort muss dann über die Methode PendingIntentHandler.setBeginGetCredentialResponse()
festgelegt werden, bevor der vorbereitete Intent als Ergebnis festgelegt wird und die Aktivität abgeschlossen ist.
Anmeldedaten-Anfragen löschen
Eine Clientanwendung kann anfordern, dass jeder für die Auswahl der Anmeldedaten beibehaltene Status gelöscht werden muss. So kann beispielsweise ein Anmeldedatenanbieter sich die zuvor ausgewählten Anmeldedaten merken und diese nur beim nächsten Mal zurückgeben. Eine Client-App ruft diese API auf und erwartet, dass die fixierte Auswahl gelöscht wird. Ihr Anmeldedatenanbieterdienst kann diese Anfrage verarbeiten, indem er die Methode onClearCredentialStateRequest()
überschreibt:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Zulassungsliste mit privilegierten Apps abrufen
Privilegierte Apps wie Webbrowser führen Aufrufe des Anmeldedaten-Managers im Namen anderer vertrauender Parteien aus. Dazu wird der Parameter origin
in den Methoden GetCredentialRequest()
und CreatePublicKeyCredentialRequest()
des Anmeldedaten-Managers festgelegt. Zur Verarbeitung dieser Anfragen ruft der Anmeldedatenanbieter die origin
mithilfe der getOrigin()
API ab.
Zum Abrufen des origin
muss die Anwendung des Anmeldedatenanbieters eine Liste privilegierter und vertrauenswürdiger Aufrufer an die androidx.credentials.provider.CallingAppInfo's getOrigin()
API übergeben. Diese Zulassungsliste muss ein gültiges JSON-Objekt sein. Der origin
wird zurückgegeben, wenn die von signingInfo
abgerufenen packageName
- und Zertifikat-Fingerabdrücke mit denen einer App übereinstimmen, die in der an die getOrigin()
API übergebenen privilegedAllowlist
gefunden wurde. Nachdem der Wert origin
abgerufen wurde, sollte die Anbieter-App dies als privilegierten Aufruf betrachten und origin
für die Clientdaten in AuthenticatorResponse
festlegen, anstatt den origin
mit der Signatur der aufrufenden App zu berechnen.
Wenn Sie ein origin
abrufen, verwenden Sie das clientDataHash
, das direkt in CreatePublicKeyCredentialRequest()
oder GetPublicKeyCredentialOption()
bereitgestellt wird, anstatt während der Signaturanfrage clientDataHash
zusammenzustellen und zu hashen.clientDataJSON
Legen Sie in der Attestierungs- und Assertion-Antwort einen Platzhalterwert für clientDataJSON
fest, um Probleme beim JSON-Parsing zu vermeiden.
Der Google Passwortmanager verwendet eine öffentlich verfügbare Zulassungsliste für Aufrufe von getOrigin()
. Als Anmeldedatenanbieter können Sie diese Liste verwenden oder Ihre eigene im JSON-Format angeben, wie in der API beschrieben. Der Anbieter entscheidet selbst, welche Liste verwendet wird. Informationen zum privilegierten Zugriff über Anmeldedaten von Drittanbietern finden Sie in der Dokumentation des Drittanbieters.
Anbieter auf einem Gerät aktivieren
Nutzer müssen den Anbieter über Geräteeinstellungen > Passwörter und Konten > Dein Anbieter > Aktivieren oder deaktivieren aktivieren.
Rufen Sie unter Android 14 oder höher die createSettingsPendingIntent()
API auf, um beim Aufrufen einen ausstehenden Intent zurückzugeben. Daraufhin wird ein Bildschirm angezeigt, über den der Nutzer den Anbieter Ihres Anmeldedaten-Managers aktivieren kann.
fun createSettingsPendingIntent(): PendingIntent