Anmeldedaten-Manager in die Lösung Ihres Anmeldedaten-Anbieters einbinden

Der Anmeldedaten-Manager bezieht sich auf eine Reihe von APIs, die in Android 14 eingeführt wurden und mehrere Anmeldemethoden wie Nutzername und 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 Anmeldedatenanbietern, die auf dem Gerät installiert sind. In diesem Dokument werden die APIs beschrieben, die Integrationsendpunkte für diese Anmeldedatenanbieter bereitstellen.

Einrichten

Bevor Sie Funktionen in Ihrem Anmeldedatenanbieter implementieren, führen Sie die Einrichtungsschritte in den folgenden Abschnitten aus.

Abhängigkeiten deklarieren

Deklarieren Sie in der build.gradle-Datei Ihres Moduls eine Abhängigkeit mit der aktuellen Version der Anmeldedaten-Manager-Bibliothek:

implementation "androidx.credentials:credentials:1.2.0-{latest}"

Dienstelement 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="<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 genannten Berechtigung und der Intent-Filter sind entscheidend, damit der Ablauf des Anmeldedaten-Managers wie erwartet funktioniert. Die Berechtigung ist erforderlich, damit nur das Android-System eine Bindung an diesen Dienst vornehmen kann. Der Intent-Filter wird für die Auffindbarkeit dieses Dienstes als Anmeldedatenanbieter verwendet, der von der Anmeldeinformationsverwaltung verwendet werden soll.

Unterstützte Anmeldedatentypen angeben

Erstellen Sie im Verzeichnis res/xml eine neue Datei mit dem Namen provider.xml. Deklarieren Sie in dieser Datei die Anmeldedatentypen, die Ihr Dienst unterstützt, über Konstanten, die für jeden Anmeldedatentyp in der Bibliothek definiert sind. Im folgenden Beispiel unterstützt der Dienst sowohl herkömmliche Passwörter als auch Passkeys, deren 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 werden Anmeldedatenanbieter in APIs wie Autofill für Passwörter und andere Daten eingebunden. Diese Anbieter können die vorhandene interne Infrastruktur zum Speichern der vorhandenen Anmeldedatentypen verwenden und gleichzeitig erweitern, um andere zu unterstützen, einschließlich Passkeys.

Zweiphasiger Ansatz für die Interaktion mit Anbietern

Der Anmeldedaten-Manager interagiert in zwei Phasen mit Anmeldedatenanbietern:

  1. Die erste Phase ist die Beginn-/Abfragephase, in der das System an Anmeldedatenanbieterdienste gebunden wird und die Methoden onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() oder onClearCredentialStateRequest() mit Begin…-Anfragen aufruft. Anbieter müssen diese Anfragen verarbeiten und mit Begin…-Antworten antworten. Sie müssen sie mit Einträgen füllen, die visuelle Optionen darstellen, die in der Kontoauswahl angezeigt werden sollen. Für jeden Eintrag muss PendingIntent festgelegt sein.
  2. Sobald der Nutzer einen Eintrag ausgewählt hat, beginnt die Auswahlphase und das mit dem Eintrag verknüpfte PendingIntent-Ereignis wird ausgelöst. Daraufhin wird die entsprechende Anbieteraktivität angezeigt. Sobald der Nutzer mit dieser Aktivität fertig ist, muss der Anmeldedatenanbieter die Antwort auf das Ergebnis der Aktivität festlegen, bevor er sie beendet. Diese Antwort wird dann an die Client-App gesendet, die den Anmeldedaten-Manager aufgerufen hat.

Passkey-Erstellung verarbeiten

Anfragen 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. Wenn Sie diese Anfrage in Ihrem Anmeldedatenanbieterdienst so verarbeiten möchten, dass der Passkey tatsächlich in Ihrem Speicher gespeichert wird, führen Sie die Schritte in den folgenden Abschnitten aus.

  1. Überschreiben Sie die onBeginCreateCredentialRequest()-Methode in Ihrem Dienst, der von CredentialProviderService erweitert wurde.
  2. Verarbeite die BeginCreateCredentialRequest, indem du eine entsprechende BeginCreateCredentialResponse erstellst und sie über den Callback weitergibst.
  3. Fügen Sie beim Erstellen der BeginCreateCredentialResponse die erforderliche CreateEntries hinzu. Jede CreateEntry sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können. Außerdem muss PendingIntent zusammen mit anderen erforderlichen Metadaten festgelegt sein.

Das folgende Beispiel veranschaulicht die Implementierung dieser Schritte.

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 PendingIntent-Konstruktion muss Folgendes erfüllen:

  • 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 beim Aufrufen der entsprechenden Aktivität benötigt, sollten als Extra für den Intent festgelegt werden, mit dem Ihre PendingIntent erstellt wird, z. B. ein accountId im Erstellungsvorgang.
  • Ihre PendingIntent muss mit dem Flag PendingIntent.FLAG_MUTABLE erstellt werden, damit das System die endgültige Anfrage an das Intent-Extra anhängen kann.
  • PendingIntent darf nicht mit dem Flag PendingIntent.FLAG_ONE_SHOT erstellt werden, da der Nutzer einen Eintrag auswählen, zurückgehen und ihn noch einmal auswählen kann, was dazu führt, dass PendingIntent zweimal ausgelöst wird.
  • Ihre PendingIntent muss mit einem eindeutigen Anfragecode erstellt werden, damit jeder Eintrag eine eigene entsprechende PendingIntent haben kann.

Auswahl von Einträgen für Anfragen zum Erstellen von Passkeys verarbeiten

  1. Wenn der Nutzer eine zuvor ausgefüllte CreateEntry auswählt, wird die entsprechende PendingIntent aufgerufen und der zugehörige Anbieter Activity wird erstellt.
  2. Nachdem die onCreate-Methode Ihrer Aktivität aufgerufen wurde, greifen Sie auf den zugehörigen Intent zu und übergeben Sie ihn an die PendingIntentHander-Klasse, um die ProviderCreateCredentialRequest zu erhalten.
  3. Extrahieren Sie requestJson, callingAppInfo und clientDataHash aus der Anfrage.
  4. Extrahieren Sie die lokale accountId aus dem Intent-Extra. Dies ist eine beispielhafte App-spezifische Implementierung und ist nicht erforderlich. Diese Konto-ID kann verwendet werden, um die Anmeldedaten mit dieser bestimmten Konto-ID zu speichern.
  5. Prüfen Sie die requestJson. Im folgenden Beispiel werden lokale Datenklassen wie PublicKeyCredentialCreationOptions verwendet, um die Eingabe-JSON-Datei in eine strukturierte Klasse gemäß der WebAuthn-Spezifikation umzuwandeln. Als Anmeldedatenanbieter können Sie dies durch Ihren eigenen Parser ersetzen.
  6. Prüfe den Asset-Link der anrufenden App, wenn der Anruf von einer nativen Android-App ausgeht.
  7. Eine Authentifizierungsaufforderung anzeigen Im folgenden Beispiel wird die Biometric API von Android verwendet.
  8. Wenn die Authentifizierung erfolgreich war, generieren Sie eine credentialId und ein Schlüsselpaar.
  9. Speichern Sie den privaten Schlüssel in Ihrer lokalen Datenbank unter callingAppInfo.packageName.
  10. Erstelle eine JSON-Antwort der Web Authentication API, die aus dem öffentlichen Schlüssel und der credentialId besteht. Im Beispiel unten werden lokale Dienstprogrammklassen wie AuthenticatorAttestationResponse und FidoPublicKeyCredential verwendet, die beim Erstellen einer JSON-Datei auf der Grundlage der oben genannten Spezifikation helfen.Als Anmeldedatenanbieter können Sie diese Klassen durch eigene Builder ersetzen.
  11. Erstellen Sie eine CreatePublicKeyCredentialResponse mit dem oben generierten JSON.
  12. Legen Sie CreatePublicKeyCredentialResponse über PendingIntentHander.setCreateCredentialResponse() als Extra für eine Intent fest und legen Sie diese Absicht als Ergebnis der Aktivität fest.
  13. Schließen Sie die Aktivität ab.

Das folgende Codebeispiel veranschaulicht diese Schritte. Dieser Code muss in Ihrer Aktivitätsklasse verarbeitet werden, sobald onCreate() aufgerufen wird.

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 zum Erstellen von Passwörtern verarbeiten

So beantwortest du Anfragen zum Erstellen von Passwörtern:

  • Fügen Sie in der im vorherigen Abschnitt erwähnten Methode processCreateCredentialRequest() im Switch-Block einen weiteren Fall für die Verarbeitung von Passwortanfragen hinzu.
  • Fügen Sie beim Erstellen der BeginCreateCredentialResponse die erforderlichen CreateEntries hinzu.
  • Jede CreateEntry sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können. Außerdem muss für sie ein PendingIntent sowie andere Metadaten festgelegt sein.

Das folgende Beispiel veranschaulicht die Implementierung dieser Schritte:

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)
}

Umgang mit der Auswahl von Einträgen für Anfragen zum Erstellen von Passwörtern

Wenn der Nutzer eine ausgefüllte CreateEntry auswählt, wird die entsprechende PendingIntent ausgeführt und die zugehörige Aktivität wird angezeigt. Rufen Sie die zugehörige Absicht auf, die in onCreate übergeben wurde, und übergeben Sie sie an die Klasse PendingIntentHander, um die Methode ProviderCreateCredentialRequest zu erhalten.

Das folgende Beispiel zeigt, wie dieser Prozess implementiert wird. Dieser Code muss in der onCreate()-Methode 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 verarbeiten

Die Nutzeranmeldung wird so verarbeitet:

  • Wenn eine Client-App versucht, einen Nutzer anzumelden, wird eine GetCredentialRequest-Instanz vorbereitet.
  • Das Android-Framework leitet diese Anfrage an alle anwendbaren Anmeldedatenanbieter weiter, indem es eine Bindung an diese Dienste herstellt.
  • Der Anbieterdienst empfängt dann eine BeginGetCredentialRequest mit einer Liste von BeginGetCredentialOption, die jeweils Parameter enthalten, mit denen übereinstimmende Anmeldedaten abgerufen werden können.

So verarbeiten Sie diese Anfrage in Ihrem Anmeldedatenanbieterdienst:

  1. Überschreibe die Methode onBeginGetCredentialRequest(), um die Anfrage zu verarbeiten. Wenn deine Anmeldedaten gesperrt sind, kannst du sofort eine AuthenticationAction in der Antwort festlegen und den Rückruf 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 vor der Rückgabe von credentialEntries erfordern, müssen einen ausstehenden Intent einrichten, der den Nutzer zum Entsperren 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
            )
        )
    }
    
  2. Rufen Sie Anmeldedaten aus Ihrer lokalen Datenbank ab und richten Sie sie mit CredentialEntries so ein, dass sie in der Auswahl angezeigt werden. Für Passkeys können Sie credentialId als zusätzliches Attribut für den Intent festlegen, damit Sie wissen, auf welche Anmeldedaten er verweist, 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)
    }
    
  3. Abmeldendaten aus Ihrer Datenbank abfragen, Passkeys und Passworteinträge erstellen, 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)
        )
    }
    
  4. Nachdem Sie die Anmeldedaten abgefragt und eingefügt 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.

Nutzerauswahl für Passkeys verarbeiten

  1. Rufen Sie in der Methode onCreate der entsprechenden Aktivität die zugehörige Absicht ab und übergeben Sie sie an PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. Extrahieren Sie die GetPublicKeyCredentialOption aus der oben abgerufenen Anfrage. Anschließend extrahieren Sie requestJson und clientDataHash aus dieser Option.
  3. Extrahieren Sie die credentialId aus dem Intent-Extra, das vom Anmeldedatenanbieter beim Einrichten der entsprechenden PendingIntent ausgefüllt wurde.
  4. Extrahieren Sie den Passkey mithilfe der oben genannten Anfrageparameter aus Ihrer lokalen Datenbank.
  5. 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.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
    )
    
  6. Um den Nutzer zu bestätigen, fordern Sie ihn zu einer biometrischen Aufforderung (oder einer anderen Bestätigungsmethode) auf. Im folgenden Code-Snippet wird die Android Biometric API verwendet.

  7. Wenn die Authentifizierung erfolgreich war, erstellen Sie eine JSON-Antwort gemäß der W3-Spezifikation für Web Authentication Assertion. Im folgenden Code-Snippet werden Hilfsdatenklassen wie AuthenticatorAssertionResponse verwendet, um strukturierte Parameter aufzunehmen und in das erforderliche JSON-Format umzuwandeln. Die Antwort enthält eine digitale Signatur aus dem privaten Schlüssel eines WebAuthn-Anmeldedatensatzes. Der Server der vertrauenden Partei kann diese Signatur überprüfen, um einen Nutzer vor der Anmeldung zu authentifizieren.

  8. Erstelle mithilfe der oben generierten JSON-Datei ein PublicKeyCredential und setze es auf ein endgültiges GetCredentialResponse. Legen Sie diese endgültige Antwort auf das Ergebnis dieser Aktivität fest.

Das folgende Beispiel veranschaulicht, 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

  1. Greifen Sie in der entsprechenden Aktivität auf den Intent zu, der an onCreate übergeben wurde, und extrahieren Sie die ProviderGetCredentialRequest mit PendingIntentHandler.
  2. Verwende GetPasswordOption in der Anfrage, um Passwort-Anmeldedaten für den Namen des eingehenden Pakets 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
        }
    }
    
  3. Nachdem Sie die Antwort abgerufen haben, legen Sie sie für die ausgewählten 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 eine AuthenticationAction festlegen, wenn die Anmeldedaten gesperrt sind. Wenn der Nutzer diesen Eintrag auswählt, wird die Aktivität aufgerufen, die der Intent-Aktion entspricht, die in PendingIntent festgelegt ist. Anmeldedatenanbieter können dann einen biometrischen Authentifizierungsablauf oder einen ähnlichen Mechanismus anzeigen, um die Anmeldedaten zu entsperren. Im Erfolgsfall muss der Anmeldedatenanbieter eine BeginGetCredentialResponse erstellen, ähnlich wie oben beschrieben, da die Anmeldedaten jetzt entsperrt sind. Diese Antwort muss dann über die Methode PendingIntentHandler.setBeginGetCredentialResponse() festgelegt werden, bevor die vorbereitete Absicht als Ergebnis festgelegt und die Aktivität beendet wird.

Anmeldedatenanfragen löschen

Eine Client-App kann anfordern, dass alle für die Anmeldedatenauswahl gespeicherten Status gelöscht werden. Ein Anmeldedatenanbieter kann beispielsweise die zuvor ausgewählten Anmeldedaten speichern und beim nächsten Mal nur diese zurückgeben. Eine Clientanwendung ruft diese API auf und erwartet, dass die angepinnte Auswahl gelöscht wird. Der Anmeldeinformationsanbieterdienst kann diese Anfrage bearbeiten, 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.
}

Damit Nutzer die Einstellungen Ihres Anbieters über den Bildschirm Passwörter, Passkeys und Autofill öffnen können, sollten Anmeldedatenanbieter-Apps das Manifestattribut credential-provider settingsActivity in res/xml/provider.xml implementieren. Mit diesem Attribut können Sie einen Intent verwenden, um den Einstellungsbildschirm Ihrer App zu öffnen, wenn ein Nutzer in der Liste der Dienste 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>
Diagramm mit den Funktionen der Schaltflächen „Ändern“ und „Öffnen“
Abbildung 1:Über die Schaltfläche Ändern wird das Auswahldialogfeld geöffnet, in dem der Nutzer seinen bevorzugten Anmeldedatenanbieter auswählen kann. Über die Schaltfläche Öffnen wird die in der Manifeständerung definierte Einstellungsaktivität gestartet und eine Seite mit Einstellungen speziell für diesen Anbieter geöffnet.

Einstellungen

Einstellungen öffnen: Durch den Intent android.settings.CREDENTIAL_PROVIDER wird ein Einstellungsbildschirm geöffnet, in dem der Nutzer seine bevorzugten und zusätzlichen Anmeldedatenanbieter auswählen kann.

Der Bildschirm „Passwörter, Passkeys und Autofill-Einstellungen“
Abbildung 2:Der Bildschirm „Passwörter, Passkeys und Autofill-Einstellungen“

Bevorzugter Anmeldedatendienst: Die ACTION_REQUEST_SET_AUTOFILL_SERVICE-Intent leitet den Nutzer zum Bildschirm zur Auswahl des bevorzugten Anbieters weiter. Der auf diesem Bildschirm ausgewählte Anbieter wird zum bevorzugten Anmeldedaten- und Autofill-Anbieter.

Diagramm mit den Funktionen der Schaltflächen „Ändern“ und „Öffnen“
Abbildung 3:Bildschirm „Bevorzugter Dienst für Passwörter, Passkeys und Autofill-Einstellungen“

Zulassungsliste für privilegierte Apps abrufen

Berechtigte Apps wie Webbrowser führen im Namen anderer vertrauenswürdiger Seiten Aufrufe des Anmeldedaten-Managers aus, indem sie den Parameter origin in den Methoden GetCredentialRequest() und CreatePublicKeyCredentialRequest() des Anmeldedaten-Managers festlegen. Zum Verarbeiten dieser Anfragen ruft der Anmeldedatenanbieter die origin mithilfe der getOrigin() API ab.

Um die origin abzurufen, muss die Anmeldedatenanbieter-App eine Liste der privilegierten und vertrauenswürdigen Anrufer an die androidx.credentials.provider.CallingAppInfo's getOrigin() API übergeben. Diese Zulassungsliste muss ein gültiges JSON-Objekt sein. Die origin wird zurückgegeben, wenn der packageName und die Zertifikatsfingerabdrücke, die von signingInfo abgerufen wurden, mit denen einer App in der privilegedAllowlist übereinstimmen, die an die getOrigin() API übergeben wurde. Nachdem der Wert origin abgerufen wurde, sollte die Anbieter-App dies als privilegierten Aufruf betrachten und origin in den Clientdaten in AuthenticatorResponse festlegen, anstatt origin anhand der Signatur der anrufenden App zu berechnen.

Wenn du eine origin abrufen möchtest, verwende die clientDataHash, die direkt in CreatePublicKeyCredentialRequest() oder GetPublicKeyCredentialOption() angegeben ist, anstatt clientDataJSON während der Signaturanfrage zusammenzustellen und zu hashen. Legen Sie in der Attestierungs- und Bestätigungsantwort einen Platzhalterwert für clientDataJSON fest, um Probleme beim JSON-Parsen 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 eine eigene im von der API beschriebenen JSON-Format angeben. Welche Liste verwendet wird, liegt im Ermessen des Anbieters. Informationen zum Erhalt von Berechtigungen bei Anmeldedatenanbietern finden Sie in der Dokumentation des jeweiligen Anbieters.

Anbieter auf einem Gerät aktivieren

Nutzer müssen den Anbieter unter Geräteeinstellungen > Passwörter und Konten > Ihr Anbieter > Aktivieren oder deaktivieren aktivieren.

fun createSettingsPendingIntent(): PendingIntent