1. Hinweis
Traditionelle Authentifizierungslösungen stellen eine Reihe von Sicherheits- und Nutzerfreundlichkeitsproblemen dar.
Passwörter werden häufig verwendet, aber…
- Leicht vergessen
- Nutzer benötigen Wissen, um starke Passwörter zu erstellen.
- Sie können von Angreifern leicht gephisht, abgerufen und wiedergegeben werden.
Android hat die Credential Manager API entwickelt, um die Anmeldung zu vereinfachen und Sicherheitsrisiken zu minimieren. Dazu werden Passkeys unterstützt, der neue Branchenstandard für die passwortlose Authentifizierung.
Der Anmeldedaten-Manager unterstützt Passkeys und kombiniert sie mit traditionellen Authentifizierungsmethoden wie Passwörtern und „Über Google anmelden“.
Nutzer können Passkeys erstellen und im Google Passwortmanager speichern. Diese Passkeys werden dann auf allen Android-Geräten synchronisiert, auf denen der Nutzer angemeldet ist. Ein Passkey muss erstellt, mit einem Nutzerkonto verknüpft und sein öffentlicher Schlüssel auf einem Server gespeichert werden, bevor sich ein Nutzer damit anmelden kann.
In diesem Codelab erfahren Sie, wie Sie sich mit Passkeys und einem Passwort über die Credential Manager API registrieren und sie für zukünftige Authentifizierungszwecke verwenden. Es gibt zwei Abläufe:
- Registrierung : Mit Passkeys und Passwort
- Anmeldung : Mit Passkeys und gespeichertem Passwort
Vorbereitung
- Grundlegende Kenntnisse zum Ausführen von Apps in Android Studio
- Grundlegende Kenntnisse des Authentifizierungsablaufs in Android-Apps
- Grundlegende Kenntnisse zu Passkeys
Lerninhalte
- So erstellen Sie einen Passkey.
- So speichern Sie ein Passwort im Passwortmanager.
- So authentifizieren Sie Nutzer mit einem Passkey oder einem gespeicherten Passwort.
Voraussetzungen
Eine der folgenden Gerätekombinationen:
- Ein Android-Gerät mit Android 9 oder höher (für Passkeys) und Android 4.4 oder höher(für die Passwortauthentifizierung über die Credential Manager API).
- Gerät mit möglichst einem biometrischen Sensor
- Registrieren Sie ein biometrisches Verfahren oder eine Displaysperre.
- Kotlin-Plug-in-Version : 1.8.10
2. Einrichten
- Klonen Sie dieses Repository auf Ihrem Laptop aus dem Branch credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
- Rufen Sie das Modul CredentialManager auf und öffnen Sie das Projekt in Android Studio.
Sehen wir uns den Anfangsstatus der App an.
So sehen Sie, wie der Anfangsstatus der App funktioniert:
- Starten Sie die App.
- Sie sehen einen Hauptbildschirm mit den Schaltflächen „Registrieren“ und „Anmelden“. Diese Schaltflächen haben noch keine Funktion, werden aber in den folgenden Abschnitten aktiviert.
3. Möglichkeit zur Registrierung mit Passkeys hinzufügen
Wenn Nutzer sich in einer Android-App, die die Credential Manager API verwendet, für ein neues Konto registrieren, können sie einen Passkey für ihr Konto erstellen. Dieser Passkey wird sicher beim ausgewählten Anmeldedatenanbieter des Nutzers gespeichert und für zukünftige Anmeldungen verwendet, ohne dass der Nutzer jedes Mal sein Passwort eingeben muss.
Jetzt erstellen Sie einen Passkey und registrieren Nutzeranmeldedaten mithilfe von Biometrie/Displaysperre.
Mit Passkey registrieren
Der Code in Credential Manager/app/main/java/SignUpFragment.kt definiert ein Textfeld „username“ und eine Schaltfläche zum Registrieren mit einem Passkey.
Übergeben Sie die Herausforderung und die andere JSON-Antwort an einen createPasskey()-Aufruf.
Bevor ein Passkey erstellt wird, müssen Sie vom Server die erforderlichen Informationen anfordern, die beim Aufruf von createCredential() an die Credential Manager API übergeben werden.
In den Assets Ihres Projekts befindet sich bereits eine Mock-Antwort namens RegFromServer.txt, die die erforderlichen Parameter in diesem Codelab zurückgibt.
- Rufen Sie in Ihrer App SignUpFragment.kt auf und suchen Sie nach der Methode signUpWithPasskeys. Dort schreiben Sie die Logik zum Erstellen eines Passkeys und zum Anmelden des Nutzers. Sie finden die Methode in derselben Klasse.
- Kommentieren Sie den else-Block, um
createPasskey()
aufzurufen, und ersetzen Sie ihn durch den folgenden Code:
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
Diese Methode wird aufgerufen, sobald ein gültiger Nutzername auf dem Bildschirm eingegeben wurde.
- Innerhalb der
createPasskey()
-Methode musst du eineCreatePublicKeyCredentialRequest()
mit den erforderlichen zurückgegebenen Parametern erstellen.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
Die fetchRegistrationJsonFromServer()
-Methode liest eine emulierte Server-PublicKeyCredentialCreationOptions
-JSON-Antwort aus Assets und gibt die Registrierungs-JSON zurück, die beim Erstellen des Passkeys übergeben werden soll.
- Suchen Sie die Methode
fetchRegistrationJsonFromServer()
und ersetzen Sie das TODO durch den folgenden Code, um JSON zurückzugeben. Entfernen Sie auch die Rückgabeanweisung für den leeren String :
SignUpFragment.kt
//TODO fetch registration mock response
val response = requireContext().readFromAsset("RegFromServer")
//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
.replace("<userName>", binding.username.text.toString())
.replace("<userDisplayName>", binding.username.text.toString())
.replace("<challenge>", getEncodedChallenge())
- Diese JSON-Datei ist unvollständig und enthält vier Felder, die ersetzt werden müssen.
- Die UserId muss eindeutig sein, damit ein Nutzer bei Bedarf mehrere Passkeys erstellen kann. Ersetzen Sie
<userId>
durch den generiertenuserId
-Wert. <challenge>
muss ebenfalls eindeutig sein, damit eine zufällige, eindeutige Herausforderung generiert wird. Die Methode ist bereits in Ihrem Code enthalten.
Eine echte Server-PublicKeyCredentialCreationOptions
-Antwort kann mehr Optionen zurückgeben. Unten finden Sie einige Beispiele für diese Felder:
{
"challenge": String,
"rp": {
"name": String,
"id": String
},
"user": {
"id": String,
"name": String,
"displayName": String
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
In der folgenden Tabelle werden einige der wichtigsten Parameter in einem PublicKeyCredentialCreationOptions
-Objekt erläutert:
Parameter | Textzeilen |
Ein servergenerierter Zufallsstring, der genügend Entropie enthält, um das Erraten zu erschweren. Sie sollte mindestens 16 Byte lang sein. Dieser Wert ist erforderlich, wird aber bei der Registrierung nicht verwendet, es sei denn, es wird eine Attestierung durchgeführt. | |
Die eindeutige ID eines Nutzers. Dieser Wert darf keine personenidentifizierbaren Informationen wie E-Mail-Adressen oder Nutzernamen enthalten. Ein zufällig generierter Wert von 16 Byte pro Konto ist gut geeignet. | |
Dieses Feld sollte eine eindeutige Kennung für das Konto enthalten, die der Nutzer erkennt, z. B. seine E-Mail-Adresse oder seinen Nutzernamen. Dieser wird in der Kontoauswahl angezeigt. Wenn Sie einen Nutzernamen verwenden, verwenden Sie denselben Wert wie bei der Passwortauthentifizierung. | |
Dieses Feld ist ein optionaler, nutzerfreundlicherer Name für das Konto. | |
Die Entität der vertrauenden Partei entspricht den Details Ihrer Anwendung. Es hat die folgenden Attribute:
| |
Liste der zulässigen Algorithmen und Schlüsseltypen. Diese Liste muss mindestens ein Element enthalten. | |
Der Nutzer, der versucht, ein Gerät zu registrieren, hat möglicherweise bereits andere Geräte registriert. Wenn Sie das Erstellen mehrerer Anmeldedaten für dasselbe Konto in einem einzigen Authenticator einschränken möchten, können Sie diese Geräte ignorieren. Das Mitglied | |
Gibt an, ob das Gerät an der Plattform befestigt werden soll oder nicht oder ob dies nicht erforderlich ist. Legen Sie diesen Wert auf | |
| Geben Sie den Wert |
Anmeldedaten erstellen
- Nachdem du einen
CreatePublicKeyCredentialRequest()
erstellt hast, musst du dencreateCredential()
-Aufruf mit der erstellten Anfrage aufrufen.
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- Sie geben die erforderlichen Informationen an
createCredential()
weiter. - Sobald die Anfrage erfolgreich war, wird auf dem Bildschirm ein Unterbrechungsbereich angezeigt, in dem Sie aufgefordert werden, einen Passkey zu erstellen.
- Nutzer können ihre Identität jetzt beispielsweise über biometrische Verfahren oder die Displaysperre bestätigen.
- Sie steuern die Sichtbarkeit der gerenderten Ansichten und die Ausnahmen, wenn die Anfrage aus irgendeinem Grund fehlschlägt. Hier werden die Fehlermeldungen protokolliert und in der App in einem Fehlerdialogfeld angezeigt. Sie können die vollständigen Fehlerprotokolle über Android Studio oder den Befehl
adb debug
prüfen.
- Abschließend müssen Sie die Registrierung abschließen. Die App sendet Anmeldedaten für öffentliche Schlüssel an den Server, der sie für den aktuellen Nutzer registriert.
Hier haben wir einen Mock-Server verwendet. Daher geben wir einfach „wahr“ zurück, um anzugeben, dass der Server den registrierten öffentlichen Schlüssel zu zukünftigen Authentifizierungs- und Validierungszwecken gespeichert hat. Weitere Informationen zur serverseitigen Passkey-Registrierung für Ihre eigene Implementierung
Suchen Sie in der Methode signUpWithPasskeys()
nach dem entsprechenden Kommentar und ersetzen Sie ihn durch den folgenden Code:
SignUpFragment.kt
//TODO : complete the registration process after sending public key credential to your server and let the user in
data?.let {
registerResponse()
DataProvider.setSignedInThroughPasskeys(true)
listener.showHome()
}
registerResponse()
gibttrue
zurück, was bedeutet, dass der Mock-Server den öffentlichen Schlüssel für die spätere Verwendung gespeichert hat.- Legen Sie für das Flag
setSignedInThroughPasskeys
den Werttrue
fest. - Nach der Anmeldung werden Nutzer zum Startbildschirm weitergeleitet.
Ein echter PublicKeyCredential
kann mehr Felder enthalten. Ein Beispiel für diese Felder ist unten zu sehen:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
In der folgenden Tabelle werden einige der wichtigsten Parameter in einem PublicKeyCredential
-Objekt erläutert:
Parameter | Textzeilen |
Eine Base64URL-codierte ID des erstellten Passkeys. Anhand dieser ID kann der Browser bei der Authentifizierung feststellen, ob sich auf dem Gerät ein passender Passkey befindet. Dieser Wert muss in der Datenbank im Backend gespeichert werden. | |
Eine | |
Clientdaten, die in einem | |
Ein mit |
Wenn Sie die App starten, können Sie auf die Schaltfläche Über Passkeys registrieren klicken und einen Passkey erstellen.
4. Passwort im Anmeldedatenanbieter speichern
In dieser App ist auf dem Bildschirm „Registrieren“ bereits eine Registrierung mit Nutzername und Passwort zu Demonstrationszwecken implementiert.
Um die Anmeldedaten des Nutzerpassworts beim Passwortanbieter zu speichern, implementieren Sie einen CreatePasswordRequest
, der an createCredential()
übergeben wird, um das Passwort zu speichern.
- Suchen Sie die
signUpWithPassword()
-Methode und ersetzen Sie das TODO durch einencreatePassword
-Aufruf:
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- In der Methode
createPassword()
müssen Sie eine Passwort-Anfrage erstellen. Ersetzen Sie das TODO durch den folgenden Code:
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- Erstellen Sie als Nächstes in der
createPassword()
-Methode Anmeldedaten mit einer Anfrage zum Erstellen eines Passworts und speichern Sie die Anmeldedaten des Nutzers beim Passwortanbieter. Ersetzen Sie das TODO durch den folgenden Code:
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(requireActivity(), request) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- Sie haben die Anmeldedaten für das Passwort beim Passwortanbieter des Nutzers gespeichert, damit er sich mit nur einem Tippen mit einem Passwort authentifizieren kann.
5. Authentifizierung mit einem Passkey oder Passwort hinzufügen
Sie können es jetzt verwenden, um sich sicher in Ihrer App zu authentifizieren.
Herausforderung und andere Optionen abrufen, die an den getPasskey()-Aufruf übergeben werden sollen
Bevor Sie den Nutzer zur Authentifizierung auffordern, müssen Sie Parameter anfordern, die Sie in WebAuthn JSON vom Server übergeben, einschließlich einer Herausforderung.
In Ihren Assets befindet sich bereits eine Mock-Antwort (AuthFromServer.txt), die in diesem Codelab solche Parameter zurückgibt.
- Rufen Sie in Ihrer App „SignInFragment.kt“ auf und suchen Sie die Methode
signInWithSavedCredentials
, in der Sie die Logik für die Authentifizierung über einen gespeicherten Passkey oder ein Passwort schreiben und den Nutzer zulassen: - Kommentieren Sie den else-Block, um
createPasskey()
aufzurufen, und ersetzen Sie ihn durch den folgenden Code:
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- In der Methode „getSavedCredentials()“ müssen Sie eine
GetPublicKeyCredentialOption()
mit den erforderlichen Parametern zum Abrufen von Anmeldedaten von Ihrem Anmeldedatenanbieter erstellen.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
Die Methode fetchAuthJsonFromServer()
liest die JSON-Authentifizierungsantwort aus den Assets und gibt JSON-Authentifizierungsdaten zurück, um alle Passkeys abzurufen, die mit diesem Nutzerkonto verknüpft sind.
Der zweite Parameter von GetPublicKeyCredentialOption() ist clientDataHash
, ein Hashwert, der zum Verifizieren der Identität der vertrauenden Seite verwendet wird. Legen Sie dies nur fest, wenn Sie den GetCredentialRequest.origin
festgelegt haben. Für die Beispiel-App ist dies auf null
festgelegt.
- Suchen Sie die Methode „fetchAuthJsonFromServer()“ und ersetzen Sie das TODO durch den folgenden Code, um JSON zurückzugeben. Entfernen Sie außerdem die Rückgabeanweisung für den leeren String:
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
Hinweis : Der Server dieses Codelabs ist so konzipiert, dass er ein JSON-Objekt zurückgibt, das dem Wörterbuch PublicKeyCredentialRequestOptions
möglichst ähnlich ist, das an den getCredential()-Aufruf der API übergeben wird. Das folgende Code-Snippet enthält einige Beispieloptionen, die in einer echten Antwort enthalten sein könnten:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
In der folgenden Tabelle werden einige der wichtigsten Parameter in einem PublicKeyCredentialRequestOptions
-Objekt erläutert:
Parameter | Textzeilen |
Eine servergenerierte Herausforderung in einem | |
Eine RP-ID ist eine Domain. Für eine Website kann entweder die Domain oder ein registrierbares Suffix angegeben werden. Dieser Wert muss mit dem Parameter |
- Als Nächstes müssen Sie ein
PasswordOption()
-Objekt erstellen, um alle in Ihrem Passwortanbieter über die Credential Manager API gespeicherten Passwörter für dieses Nutzerkonto abzurufen. Suchen Sie in der MethodegetSavedCredentials()
nach dem TODO und ersetzen Sie es durch Folgendes:
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
Anmeldedaten abrufen
- Als Nächstes musst du die
getCredential()
-Anfrage mit allen oben genannten Optionen aufrufen, um die zugehörigen Anmeldedaten abzurufen:
SignInFragment.kt
//TODO call getCredential() with required credential options
val result = try {
credentialManager.getCredential(
requireActivity(),
GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
} catch (e: Exception) {
configureViews(View.INVISIBLE, true)
Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
activity?.showErrorAlert(
"An error occurred while authenticating through saved credentials. Check logs for additional details"
)
return null
}
if (result.credential is PublicKeyCredential) {
val cred = result.credential as PublicKeyCredential
DataProvider.setSignedInThroughPasskeys(true)
return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
val cred = result.credential as PasswordCredential
DataProvider.setSignedInThroughPasskeys(false)
return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
//If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}
- Sie geben die erforderlichen Informationen an
getCredential()
weiter. Dazu werden die Anmeldedatenoptionen und ein Aktivitätskontext verwendet, um die Optionen im unteren Bereich in diesem Kontext zu rendern. - Sobald die Anfrage erfolgreich war, wird auf dem Bildschirm eine Bottomsheet mit allen erstellten Anmeldedaten für das verknüpfte Konto angezeigt.
- Nutzer können jetzt ihre Identität über biometrische Verfahren oder die Displaysperre bestätigen, um die ausgewählten Anmeldedaten zu authentifizieren.
- Wenn die ausgewählten Anmeldedaten
PublicKeyCredential
sind, setzen Sie dassetSignedInThroughPasskeys
-Flag auftrue
. Setzen Sie es andernfalls auffalse
.
Das folgende Code-Snippet enthält ein Beispiel für ein PublicKeyCredential
-Objekt:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
Die folgende Tabelle ist nicht vollständig, enthält aber die wichtigsten Parameter im PublicKeyCredential
-Objekt:
Parameter | Textzeilen |
Die Base64URL-codierte ID des authentifizierten Passkeys. | |
Eine | |
Ein | |
Ein | |
Ein | |
Ein |
- Abschließend müssen Sie den Authentifizierungsvorgang abschließen. Normalerweise sendet die App nach Abschluss der Passkey-Authentifizierung einen öffentlichen Schlüssel mit einer Authentifizierungsbestätigung an den Server, der die Bestätigung überprüft und den Nutzer authentifiziert.
Hier haben wir einen Mock-Server verwendet, sodass wir einfach true
zurückgeben, was bedeutet, dass der Server die Behauptung bestätigt hat. Weitere Informationen zur serverseitigen Passkey-Authentifizierung für Ihre eigene Implementierung
Suchen Sie in der Methode signInWithSavedCredentials()
den entsprechenden Kommentar und ersetzen Sie ihn durch den folgenden Code:
SignInFragment.kt
//TODO : complete the authentication process after validating the public key credential to your server and let the user in.
data?.let {
sendSignInResponseToServer()
listener.showHome()
}
sendSigninResponseToServer()
gibt „wahr“ zurück, was bedeutet, dass der (Mock-)Server den öffentlichen Schlüssel für die zukünftige Verwendung validiert hat.- Nach der Anmeldung werden Nutzer zum Startbildschirm weitergeleitet.
Öffnen Sie die App und gehen Sie zu Anmelden > Mit Passkeys/gespeichertem Passwort anmelden. Versuchen Sie,sich mit den gespeicherten Anmeldedaten anzumelden.
Ausprobieren
Sie haben in Ihrer Android-App die Erstellung von Passkeys, das Speichern von Passwörtern im Anmeldedaten-Manager und die Authentifizierung über Passkeys oder gespeicherte Passwörter mit der Anmeldedaten-Manager API implementiert.
6. Glückwunsch!
Sie haben dieses Codelab abgeschlossen. Die endgültige Lösung finden Sie unter https://github.com/android/identity-samples/tree/main/CredentialManager.
Wenn Sie Fragen haben, stellen Sie sie in Stack Overflow mit dem Tag passkey
.