Der Credential Manager ist eine Reihe von APIs, die in Android 14 eingeführt wurden und mehrere Anmeldemethoden unterstützen, z. B. Nutzername und Passwort, Passkeys und Verbundanmeldungen (z. B. „Über Google anmelden“). Wenn die Credential Manager API aufgerufen wird, fasst das Android-System die Anmeldedaten aller auf dem Gerät installierten Anbieter von Anmeldedaten zusammen. In diesem Dokument werden die APIs beschrieben, die Integrationsendpunkte für diese Anbieter von Anmeldedaten bereitstellen.
Einrichtung
Bevor Sie Funktionen in Ihrem Anmeldedatenanbieter implementieren, führen Sie die in den folgenden Abschnitten beschriebenen Einrichtungsschritte aus.
Abhängigkeiten deklarieren
Fügen Sie dem Build-Script Ihres App-Moduls die folgenden Abhängigkeiten hinzu, um die aktuelle Version der Credential Manager-Bibliothek zu verwenden:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" }
Dienst-Element in der Manifestdatei deklarieren
Fügen Sie in der Manifestdatei AndroidManifest.xml Ihrer 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="@mipmap/ic_launcher"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
tools:targetApi="upside_down_cake">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
Die Berechtigung und der Intent-Filter, die im vorherigen Beispiel gezeigt wurden, sind für die ordnungsgemäße Funktion des Credential Manager-Ablaufs unerlässlich. Die Berechtigung ist erforderlich, damit nur das Android-System eine Bindung an diesen Dienst vornehmen kann. Der Intent-Filter wird verwendet, um diesen Dienst als Anbieter von Anmeldedaten für den Credential Manager zu finden.
Unterstützte Anmeldedatentypen deklarieren
Erstellen Sie in Ihrem res/xml-Verzeichnis eine neue Datei mit dem Namen provider.xml. In dieser Datei deklarieren Sie die von Ihrem Dienst unterstützten Arten von Anmeldedaten über Konstanten, die für jede Art von Anmeldedaten in der Bibliothek definiert sind. Im folgenden Beispiel unterstützt der Dienst sowohl herkömmliche Passwörter als auch Passkeys. Die Konstanten dafür sind als TYPE_PASSWORD_CREDENTIAL und TYPE_PUBLIC_KEY_CREDENTIAL definiert:
<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 werden Anbieter von Anmeldedaten in APIs wie Autofill für Passwörter und andere Daten eingebunden. Diese Anbieter können dieselbe interne Infrastruktur verwenden, um die vorhandenen Anmeldedatentypen zu speichern, und sie gleichzeitig erweitern, um andere zu unterstützen, einschließlich Passkeys.
Zweistufiger Ansatz für die Interaktion mit Anbietern
Der Credential Manager interagiert in zwei Phasen mit Anmeldedatenanbietern:
- Die erste Phase ist die Beginn-/Abfragephase, in der das System eine Bindung zu Diensten von Anbietern von Anmeldedaten herstellt und die Methoden
onBeginGetCredentialRequest(),onBeginCreateCredentialRequest()oderonClearCredentialStateRequest()mitBegin…-Anfragen aufruft. Anbieter müssen diese Anfragen verarbeiten und mitBegin…-Antworten antworten, die mit Einträgen gefüllt sind, die visuelle Optionen darstellen, die in der Kontoauswahl angezeigt werden sollen. Für jeden Eintrag muss einPendingIntentfestgelegt sein. - Sobald der Nutzer einen Eintrag auswählt, beginnt die Auswahlphase und der mit dem Eintrag verknüpfte
PendingIntentwird ausgelöst, wodurch die entsprechende Anbieteraktivität aufgerufen wird. Sobald der Nutzer die Interaktion mit dieser Aktivität abgeschlossen 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 Client-App gesendet, die den Credential Manager aufgerufen hat.
Passkey-Erstellung verarbeiten
Anfragen zur Passkey-Erstellung verarbeiten
Wenn eine Client-App einen Passkey erstellen und bei einem Anbieter von Anmeldedaten speichern möchte, ruft sie die createCredential API auf. Führen Sie die in den folgenden Abschnitten beschriebenen Schritte aus, um diese Anfrage in Ihrem Dienst für die Bereitstellung von Anmeldedaten so zu verarbeiten, dass der Passkey tatsächlich in Ihrem Speicher gespeichert wird.
- Überschreiben Sie die Methode
onBeginCreateCredentialRequest()in Ihrem Dienst, der vonCredentialProviderServiceerweitert wurde. - Verarbeite die
BeginCreateCredentialRequest, indem du eine entsprechendeBeginCreateCredentialResponseerstellst und sie über den Callback übergibst. - Fügen Sie beim Erstellen des
BeginCreateCredentialResponsedie erforderlicheCreateEntrieshinzu. JedesCreateEntry-Objekt sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und muss neben anderen erforderlichen Metadaten einenPendingIntent-Wert enthalten.
Das folgende Beispiel zeigt, 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
)
)
}
Ihre PendingIntent-Konstruktion sollte Folgendes beachten:
- Die entsprechende Aktivität sollte so eingerichtet sein, 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 des
PendingIntentverwendet wird, z. B. einaccountIdim Erstellungsablauf. - Ihr
PendingIntentmuss mit dem FlagPendingIntent.FLAG_MUTABLEerstellt werden, damit das System die endgültige Anfrage an das Intent-Extra anhängen kann. - Ihr
PendingIntentdarf nicht mit dem FlagPendingIntent.FLAG_ONE_SHOTerstellt werden, da der Nutzer einen Eintrag auswählen, zurückgehen und ihn noch einmal auswählen kann, was dazu führen würde, dass derPendingIntentzweimal ausgelöst wird. - Ihr
PendingIntentmuss mit einem eindeutigen Anfragecode erstellt werden, damit jeder Eintrag einen eigenen entsprechendenPendingIntenthaben kann.
Eintragsauswahl für Anfragen zur Passkey-Erstellung verarbeiten
- Wenn der Nutzer einen zuvor ausgefüllten
CreateEntryauswählt, wird der entsprechendePendingIntentaufgerufen und der zugehörige AnbieterActivityerstellt. - Nachdem die Methode
onCreateIhrer Aktivität aufgerufen wurde, greifen Sie auf den zugehörigen Intent zu und übergeben Sie ihn an die KlassePendingIntentHander, um dieProviderCreateCredentialRequestabzurufen. - Extrahieren Sie
requestJson,callingAppInfoundclientDataHashaus der Anfrage. - Extrahiere die lokale
accountIdaus dem Intent-Extra. Dies ist eine Beispiel-App-spezifische Implementierung und nicht erforderlich. Diese Konto-ID kann verwendet werden, um diesen Berechtigungsnachweis für diese bestimmte Konto-ID zu speichern. - Validieren Sie die
requestJson. Im folgenden Beispiel werden lokale Datenklassen wiePublicKeyCredentialCreationOptionsverwendet, um die JSON-Eingabe gemäß der WebAuthn-Spezifikation in eine strukturierte Klasse zu konvertieren. Als Anbieter von Anmeldedaten können Sie dies durch Ihren eigenen Parser ersetzen. - Prüfe den Asset-Link für die Anruf-App, wenn der Anruf von einer nativen Android-App stammt.
- Eine Authentifizierungsaufforderung anzeigen. Im folgenden Beispiel wird die Biometric API von Android verwendet.
- Wenn die Authentifizierung erfolgreich ist, generieren Sie einen
credentialIdund ein Schlüsselpaar. - Speichern Sie den privaten Schlüssel in Ihrer lokalen Datenbank unter
callingAppInfo.packageName. - Erstellen Sie eine JSON-Antwort der Web Authentication API, die aus dem öffentlichen Schlüssel und dem
credentialIdbesteht. Im folgenden Beispiel werden lokale Dienstprogrammklassen wieAuthenticatorAttestationResponseundFidoPublicKeyCredentialverwendet, mit denen ein JSON-Objekt basierend auf der zuvor erwähnten Spezifikation erstellt werden kann.Als Anbieter von Anmeldedaten können Sie diese Klassen durch Ihre eigenen Builder ersetzen. - Erstellen Sie einen
CreatePublicKeyCredentialResponsemit dem oben generierten JSON. - Lege
CreatePublicKeyCredentialResponseals Extra für einenIntentüberPendingIntentHander.setCreateCredentialResponse()fest und setze diesen Intent auf das Ergebnis der Aktivität. - Beende die Aktivität.
Das folgende Codebeispiel veranschaulicht diese Schritte. Dieser Code muss in Ihrer Aktivitätsklasse verarbeitet werden, sobald onCreate() aufgerufen wird.
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
// ...
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
)
}
}
@SuppressLint("RestrictedApi")
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
{ }, // Pass in your own executor
object : AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
@RequiresApi(VERSION_CODES.P)
override fun onAuthenticationSucceeded(
result: 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result,
createPublicKeyCredResponse
)
setResult(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)
}
@RequiresApi(VERSION_CODES.P)
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)}"
}
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()einen weiteren Fall im Switch-Block hinzu, um Passwortanfragen zu verarbeiten. - Fügen Sie beim Erstellen des
BeginCreateCredentialResponsedie erforderlichenCreateEntrieshinzu. - Jedes
CreateEntrysollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und muss zusammen mit anderen Metadaten einenPendingIntententhalten.
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
}
@RequiresApi(VERSION_CODES.M)
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 des Eintrags für Anfragen zur Passworterstellung verarbeiten
Wenn der Nutzer ein ausgefülltes CreateEntry auswählt, wird der entsprechende PendingIntent ausgeführt und die zugehörige Aktivität aufgerufen. Greifen Sie auf den zugehörigen Intent zu, 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)
if (createRequest == null) {
return
}
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
mDatabase.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)
finish()
Nutzeranmeldung verarbeiten
Die Nutzeranmeldung erfolgt in den folgenden Schritten:
- Wenn eine Client-App versucht, einen Nutzer anzumelden, bereitet sie eine
GetCredentialRequest-Instanz vor. - Das Android-Framework leitet diese Anfrage an alle relevanten Anbieter von Anmeldedaten weiter, indem es eine Bindung an diese Dienste herstellt.
- Der Anbieterdienst empfängt dann eine
BeginGetCredentialRequest, die eine Liste vonBeginGetCredentialOptionenthält. Jede davon enthält Parameter, mit denen übereinstimmende Anmeldedaten abgerufen werden können.
Führen Sie die folgenden Schritte aus, um diese Anfrage in Ihrem Dienst für den Anmeldedatenanbieter zu verarbeiten:
Überschreiben Sie die Methode
onBeginGetCredentialRequest(), um die Anfrage zu verarbeiten. Wenn Ihre Anmeldedaten gesperrt sind, können Sie sofort einenAuthenticationActionfü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, die das Entsperren der Anmeldedaten erfordern, bevor
credentialEntrieszurückgegeben werden, 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 die Anmeldedaten aus Ihrer lokalen Datenbank ab und richten Sie sie mit
CredentialEntriesein, damit sie in der Auswahl angezeigt werden. Bei Passkeys kannst ducredentialIdals Extra für den Intent festlegen, um zu wissen, 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 processGetCredentialRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackageInfo = request.callingAppInfo val callingPackageName = callingPackageInfo?.packageName.orEmpty() val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackageName, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackageInfo, 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 ), beginGetPublicKeyCredentialOption = 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 abgefragt und ausgefüllt haben, müssen Sie die Auswahlphase für die vom Nutzer ausgewählten Anmeldedaten verarbeiten, unabhängig davon, ob es sich um einen Passkey oder ein Passwort handelt.
Nutzer-Passkey-Auswahl verarbeiten
- Rufen Sie in der Methode
onCreateder entsprechenden Aktivität den zugehörigen Intent ab und übergeben Sie ihn anPendingIntentHandler.retrieveProviderGetCredentialRequest(). - Extrahieren Sie die
GetPublicKeyCredentialOptionaus der oben abgerufenen Anfrage. Extrahieren Sie anschließendrequestJsonundclientDataHashaus dieser Option. - Extrahiere die
credentialIdaus dem Intent-Extra, das vom Anbieter von Anmeldedaten beim Einrichten des entsprechendenPendingIntentausgefüllt wurde. - Extrahieren Sie den Passkey aus Ihrer lokalen Datenbank. Verwenden Sie dazu die oben abgerufenen Anfrageparameter.
Prüfen Sie, ob der Passkey mit den extrahierten Metadaten und der Nutzerbestätigung gültig ist.
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo?.getString("credId").orEmpty() // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest val passkey = mDatabase.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 )Zeige eine Aufforderung zur biometrischen Authentifizierung (oder eine andere Bestätigungsmethode) an, um den Nutzer zu validieren. Im folgenden Code-Snippet wird die Android Biometric API verwendet.
Nach erfolgreicher Authentifizierung wird eine JSON-Antwort basierend auf der W3 Web Authentication Assertion-Spezifikation erstellt. Im folgenden Code-Snippet werden Hilfsdatenklassen wie
AuthenticatorAssertionResponseverwendet, um strukturierte Parameter zu übernehmen und in das erforderliche JSON-Format zu konvertieren. Die Antwort enthält eine digitale Signatur des privaten Schlüssels eines WebAuthn-Anmeldedatensatzes. Der Server der vertrauenden Partei kann diese Signatur prüfen, um einen Nutzer vor der Anmeldung zu authentifizieren.Erstellen Sie mit dem oben generierten JSON-Code ein
PublicKeyCredentialund legen Sie es in einem endgültigenGetCredentialResponsefest. 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,
{ }, // Pass in your own 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,
authenticatorAttachment = "", // Add your authenticator attachment
)
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)
Nutzer-Auswahl für die Passwortauthentifizierung verarbeiten
- Greifen Sie in der entsprechenden Aktivität auf den Intent zu, der an
onCreateübergeben wurde, und extrahieren Sie dieProviderGetCredentialRequestmitPendingIntentHandler. Verwenden Sie
GetPasswordOptionin der Anfrage, um Passwort-Anmeldedaten für den eingehenden Paketnamen abzurufen.val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption val username = passwordOption.allowedUserIds.first() // Fetch the credentials for the calling app package name val creds = mDatabase.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext()) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }Legen Sie nach dem Abrufen die Antwort für die ausgewählten Passwort-Anmeldedaten 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 Authentifizierungsaktionseintrags 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 im PendingIntent festgelegten Intent-Aktion entspricht. Anmeldedatenanbieter können dann einen biometrischen Authentifizierungsvorgang oder einen ähnlichen Mechanismus zum Entsperren der Anmeldedaten anzeigen. Bei Erfolg muss der Anbieter der Anmeldedaten ein BeginGetCredentialResponse erstellen, ähnlich wie oben für die Verarbeitung der Nutzeranmeldung beschrieben, da die Anmeldedaten jetzt entsperrt sind. Diese Antwort muss dann über die Methode PendingIntentHandler.setBeginGetCredentialResponse() festgelegt werden, bevor der vorbereitete Intent als Ergebnis festgelegt und die Aktivität beendet wird.
Anfragen für Anmeldedaten löschen
Eine Client-App kann anfordern, dass alle für die Auswahl von Anmeldedaten gespeicherten Status gelöscht werden. Ein Anbieter von Anmeldedaten kann sich beispielsweise die zuvor ausgewählten Anmeldedaten merken und beim nächsten Mal nur diese zurückgeben. Eine Client-App ruft diese API auf und erwartet, dass die persistente Auswahl gelöscht wird. Ihr Credential Provider-Dienst kann diese Anfrage verarbeiten, indem er die Methode onClearCredentialStateRequest() überschreibt:
override fun onClearCredentialStateRequest(
request: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>
) {
// Delete any maintained state as appropriate.
}
Möglichkeit zum Verlinken auf die Einstellungsseite des Anbieters hinzufügen
Damit Nutzer die Einstellungen deines Anbieters über den Bildschirm Passwörter, Passkeys und Autofill öffnen können, sollten Apps von Anbietern von Anmeldedaten das Manifestattribut credential-provider settingsActivity in res/xml/provider.xml implementieren. Mit diesem Attribut kannst du einen Intent verwenden, um den Einstellungsbildschirm deiner App zu öffnen, wenn ein Nutzer in der Liste der Dienste unter Passwörter, Passkeys und Autofill auf den Namen eines Anbieters klickt. Legen Sie den Wert dieses Attributs auf den Namen der Aktivität fest, die über den Einstellungsbildschirm gestartet werden soll.
<credential-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsSubtitle="Example settings provider name"
android:settingsActivity="com.example.SettingsActivity">
<capabilities>
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
Einstellungen
Einstellungen öffnen: Mit dem Intent android.settings.CREDENTIAL_PROVIDER wird ein Einstellungsbildschirm aufgerufen, auf dem der Nutzer seinen bevorzugten und zusätzliche Anbieter von Anmeldedaten auswählen kann.
Bevorzugter Dienst für Anmeldedaten: Der Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE leitet den Nutzer zum Bildschirm für die Auswahl des bevorzugten Anbieters weiter. Der auf diesem Bildschirm ausgewählte Anbieter wird zum bevorzugten Anbieter für Anmeldedaten und Autofill.
Zulassungsliste privilegierter Apps abrufen
Privilegierte Apps wie Webbrowser rufen den Credential Manager im Namen anderer vertrauender Seiten auf, indem sie den Parameter origin in den Credential Manager-Methoden GetCredentialRequest() und CreatePublicKeyCredentialRequest() festlegen. Zur Verarbeitung dieser Anfragen ruft der Anbieter von Anmeldedaten die origin mithilfe der getOrigin() API ab.
Um die origin abzurufen, muss die App 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 der packageName und die von signingInfo abgerufenen Zertifikat-Fingerabdrücke mit denen einer App übereinstimmen, die in der privilegedAllowlist gefunden wurde, die an die getOrigin() API übergeben wurde. Nachdem der origin-Wert abgerufen wurde, sollte die Anbieter-App dies als privilegierten Aufruf betrachten und diesen origin für die Clientdaten in der AuthenticatorResponse festlegen, anstatt den origin-Wert mithilfe der Signatur der aufrufenden App zu berechnen.
Wenn Sie einen origin abrufen, verwenden Sie den clientDataHash, der direkt in CreatePublicKeyCredentialRequest() oder GetPublicKeyCredentialOption() bereitgestellt wird, anstatt clientDataJSON während der Signaturanfrage zusammenzustellen und zu hashen. Um Probleme beim JSON-Parsing zu vermeiden, legen Sie in der Attestierungs- und Assertion-Antwort einen Platzhalterwert für clientDataJSON fest. Der Google Passwortmanager verwendet eine öffentlich verfügbare Zulassungsliste für Aufrufe von getOrigin(). Als Anbieter von Anmeldedaten können Sie diese Liste verwenden oder eine eigene Liste im von der API beschriebenen JSON-Format bereitstellen. Der Anbieter entscheidet, welche Liste verwendet wird. Informationen zum Erhalt von privilegiertem Zugriff mit Drittanbieter-Anmeldedaten finden Sie in der Dokumentation des Drittanbieters.
Anbieter auf einem Gerät aktivieren
Nutzer müssen den Anbieter über die Geräteeinstellungen > Passwörter und Konten > Ihr Anbieter > Aktivieren oder Deaktivieren aktivieren.
fun createSettingsPendingIntent(): PendingIntent