1. Zanim zaczniesz
Tradycyjne rozwiązania do uwierzytelniania stwarzają wiele problemów związanych z bezpieczeństwem i użytecznością.
Hasła są powszechnie używane, ale…
- Łatwo zapomnieć
- Użytkownicy muszą wiedzieć, jak tworzyć silne hasła.
- Łatwe do wyłudzenia, zebrania i odtwarzania przez atakujących.
Android stworzył interfejs Credential Manager API, aby uprościć proces logowania i zmniejszyć zagrożenia dla bezpieczeństwa dzięki obsłudze kluczy dostępu, czyli standardu branżowego nowej generacji w zakresie uwierzytelniania bez hasła.
Credential Manager łączy obsługę kluczy dostępu z tradycyjnymi metodami uwierzytelniania, takimi jak hasła czy logowanie przez Google.
Użytkownicy będą mogli tworzyć klucze dostępu i przechowywać je w Menedżerze haseł Google, który będzie synchronizować te klucze na urządzeniach z Androidem, na których użytkownik jest zalogowany. Zanim użytkownik będzie mógł zalogować się za pomocą klucza dostępu, musi go utworzyć, powiązać z kontem użytkownika i zapisać jego klucz publiczny na serwerze.
Z tego samouczka dowiesz się, jak zarejestrować się za pomocą kluczy dostępu i hasła przy użyciu interfejsu Credential Manager API oraz jak używać ich w przyszłości do uwierzytelniania. Dostępne są 2 procesy, w tym:
- Rejestracja : przy użyciu kluczy dostępu i hasła.
- Logowanie się : za pomocą kluczy dostępu i zapisanych haseł.
Wymagania wstępne
- Podstawowa wiedza o uruchamianiu aplikacji w Android Studio.
- Podstawowa wiedza o procesie uwierzytelniania w aplikacjach na Androida.
- Podstawowa wiedza o kluczach dostępu.
Czego się nauczysz
- Jak utworzyć klucz dostępu
- Jak zapisać hasło w menedżerze haseł.
- Jak uwierzytelniać użytkowników za pomocą klucza dostępu lub zapisanego hasła.
Czego potrzebujesz
Jedna z tych kombinacji urządzeń:
- urządzenie z Androidem w wersji 9 lub nowszej (w przypadku kluczy dostępu) oraz w wersji 4.4 lub nowszej(w przypadku uwierzytelniania za pomocą hasła przez interfejs Credential Manager API);
- Urządzenie najlepiej z czujnikiem biometrycznym.
- Zarejestruj blokadę ekranu (biometryczną lub inną).
- Wersja wtyczki Kotlin : 1.8.10
2. Konfiguracja
Ta przykładowa aplikacja wymaga połączenia zasobu cyfrowego z witryną, aby Menedżer danych logowania mógł zweryfikować połączenie i kontynuować działanie. Dlatego identyfikator rp użyty w odpowiedziach symulacyjnych pochodzi z symulowanego serwera zewnętrznego. Jeśli chcesz wypróbować własną odpowiedź testową, dodaj domenę aplikacji i nie zapomnij o połączeniu zasobów cyfrowych, o którym piszemy tutaj.
Użyj tego samego pliku debug.keystore, który został wymieniony w projekcie, aby utworzyć warianty debugowania i wersji. Dzięki temu zweryfikujesz na serwerze testowym połączenie zasobu cyfrowego z nazwą pakietu i odciskiem cyfrowym SHA. (W przypadku przykładowej aplikacji w pliku build.gradle jest to już zrobione).
- Sklonuj to repozytorium na laptopie z gałęzi credman_codelab: https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
- Otwórz moduł CredentialManager i otwórz projekt w Android Studio.
Sprawdzanie stanu początkowego aplikacji
Aby zobaczyć, jak działa stan początkowy aplikacji, wykonaj te czynności:
- Uruchom aplikację.
- Widzisz ekran główny z przyciskami rejestracji i logowania. Te przyciski jeszcze nie działają, ale w kolejnych sekcjach włączymy ich funkcjonalność.

3. Dodawanie możliwości rejestracji za pomocą kluczy dostępu
Podczas rejestracji nowego konta w aplikacji na Androida, która korzysta z interfejsu Credential Manager API, użytkownicy mogą utworzyć klucz dostępu do swojego konta. Klucz dostępu zostanie bezpiecznie zapisany u wybranego dostawcy danych logowania i będzie używany do logowania w przyszłości bez konieczności wpisywania hasła za każdym razem.
Teraz utworzysz klucz dostępu i zarejestrujesz dane logowania użytkownika za pomocą danych biometrycznych lub blokady ekranu.
Rejestracja za pomocą klucza dostępu
Kod w CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt definiuje pole tekstowe „username” (nazwa użytkownika) i przycisk rejestracji za pomocą klucza dostępu.

Zdefiniuj funkcję lambda createCredential() do użycia w modelach widoku
Obiekty menedżera danych logowania wymagają przekazania obiektu Activity powiązanego z obiektem Screen. Operacje Menedżera danych logowania są jednak zwykle wywoływane w modelach widoku, a odwoływanie się do aktywności w modelach widoku nie jest zalecane. Dlatego definiujemy funkcje menedżera danych logowania w osobnym pliku CredentialManagerUtil.kt i odwołujemy się do nich na odpowiednich ekranach, które następnie przekazują je do modeli widoku jako wywołania zwrotne za pomocą funkcji lambda.
Znajdź komentarz TODO w funkcji createCredential() w pliku CredentialManagerUtil.kt i wywołaj funkcję CredentialManager.create():
CredentialManagerUtil.kt
suspend fun createCredential(
activity: Activity,
request: CreateCredentialRequest
): CreateCredentialResponse {
TODO("Create a CredentialManager object and call createCredential() with a CreateCredentialRequest")
val credentialManager = CredentialManager.create(activity)
return credentialManager.createCredential(activity, request)
}
Przekaż wyzwanie i inną odpowiedź w formacie JSON do wywołania createPasskey().
Zanim utworzysz klucz dostępu, musisz poprosić serwer o informacje niezbędne do przekazania do interfejsu Credential Manager API podczas wywołania createCredential().
W zasobach projektu masz już odpowiedź testową o nazwie RegFromServer.txt, która zwraca parametry wymagane w tym laboratorium.
- W aplikacji otwórz
SignUpViewModel.kt. Znajdź metodęsignUpWithPasskeys, w której napiszesz logikę tworzenia klucza dostępu i umożliwiania użytkownikowi logowania się. Metodę znajdziesz w tej samej klasie. - Znajdź blok komentarzy
TODOdocreate a CreatePublicKeyCredentialRequest()i zastąp go tym kodem:
SignUpViewModel.kt
TODO("Create a CreatePublicKeyCredentialRequest() with necessary registration json from server")
val request = CreatePublicKeyCredentialRequest(
jsonProvider.fetchRegistrationJson()
.replace("<userId>", getEncodedUserId())
.replace("<userName>", _username.value)
.replace("<userDisplayName>", _username.value)
.replace("<challenge>", getEncodedChallenge())
)
Metoda jsonProvider.fetchRegistrationJsonFromServer() odczytuje z zasobów odpowiedź JSON z emulowanego serwera PublicKeyCredentialCreationOptions i zwraca rejestracyjny JSON, który ma być przekazywany podczas tworzenia klucza dostępu. Zastępujemy niektóre wartości symboli zastępczych wpisami użytkowników z naszej aplikacji i niektórymi polami symulowanymi:
- Ten plik JSON jest niekompletny i zawiera 4 pola, które należy zastąpić.
- Identyfikator użytkownika musi być unikalny, aby użytkownik mógł utworzyć wiele kluczy dostępu (w razie potrzeby). Zastąp
<userId>wygenerowaną wartościąuserId. <challenge>musi być też unikalny, więc wygenerujesz losowe, niepowtarzalne wyzwanie. Metoda jest już w Twoim kodzie.
Odpowiedź prawdziwego serwera PublicKeyCredentialCreationOptions może zawierać więcej opcji. Przykłady niektórych z tych pól znajdziesz poniżej:
{
"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"
}
}
W tabeli poniżej znajdziesz opis niektórych ważnych parametrów obiektu PublicKeyCredentialCreationOptions:
Parametry | Teksty reklam |
Ciąg losowy wygenerowany przez serwer, który zawiera wystarczającą entropię, aby uniemożliwić jego odgadnięcie. Powinien mieć co najmniej 16 bajtów. Jest to wymagane, ale nieużywane podczas rejestracji, chyba że wykonujesz atest. | |
Unikalny identyfikator użytkownika. Ta wartość nie może zawierać informacji umożliwiających identyfikację, np. adresów e-mail czy nazw użytkowników. Wystarczy losowa 16-bajtowa wartość wygenerowana dla każdego konta. | |
To pole powinno zawierać unikalny identyfikator konta, który użytkownik rozpozna, np. adres e-mail lub nazwę użytkownika. Będzie on wyświetlany w selektorze kont. (Jeśli używasz nazwy użytkownika, użyj tej samej wartości co w przypadku uwierzytelniania za pomocą hasła). | |
To pole zawiera opcjonalną, bardziej przyjazną dla użytkownika nazwę konta. | |
Podmiot Relying Party odpowiada szczegółom Twojej aplikacji. Obejmuje te atrybuty:
| |
Lista dozwolonych algorytmów i typów kluczy. Ta lista musi zawierać co najmniej 1 element. | |
Użytkownik, który próbuje zarejestrować urządzenie, mógł już zarejestrować inne urządzenia. Aby ograniczyć tworzenie wielu danych logowania do tego samego konta na jednym uwierzytelnianiu, możesz zignorować te urządzenia. Element | |
Określa, czy urządzenie powinno być przymocowane do platformy, czy nie, lub czy nie ma takiego wymogu. Ustaw tę wartość na | |
| wpisz wartość |
Tworzenie danych logowania
- Po utworzeniu
CreatePublicKeyCredentialRequest()musisz wywołać wywołaniecreateCredential()z utworzonym żądaniem.
SignUpViewModel.kt
try {
TODO("Call createCredential() with createPublicKeyCredentialRequest")
createCredential(request)
TODO("Complete the registration process after sending public key credential to your server and let the user in")
} catch (e: CreateCredentialException) {
handlePasskeyFailure(e)
}
- Odpowiadasz za widoczność renderowanych widoków i obsługujesz wyjątki, jeśli żądanie nie powiedzie się z jakiegoś powodu. Komunikaty o błędach są rejestrowane i wyświetlane w aplikacji w oknie błędu. Pełne logi błędów możesz sprawdzić w Android Studio lub za pomocą polecenia
adb debug.

- Na koniec musisz dokończyć proces rejestracji. Aplikacja wysyła do serwera dane logowania za pomocą klucza publicznego, który rejestruje je dla bieżącego użytkownika.
Użyliśmy tutaj serwera testowego, więc zwracamy wartość „true”, co oznacza, że serwer zapisał zarejestrowany klucz publiczny na potrzeby przyszłego uwierzytelniania i weryfikacji. Więcej informacji o rejestracji kluczy dostępu po stronie serwera znajdziesz w dokumentacji dotyczącej własnej implementacji.
W metodzie signUpWithPasskeys() znajdź odpowiedni komentarz i zastąp go tym kodem:
SignUpViewModel.kt
try {
createCredential(request)
TODO("Complete the registration process after sending public key credential to your server and let the user in")
registerResponse()
DataProvider.setSignedInThroughPasskeys(true)
_navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = true))
} catch (e: CreateCredentialException) {
handlePasskeyFailure(e)
}
registerResponse()zwracatrue, co oznacza, że serwer testowy zapisał klucz publiczny do wykorzystania w przyszłości.- Ustaw flagę
setSignedInThroughPasskeysnatrue. - Po zalogowaniu przekierowujesz użytkownika na ekran główny.
Prawdziwy PublicKeyCredential może zawierać więcej pól. Przykład tych pól znajdziesz poniżej:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
W tabeli poniżej znajdziesz opis niektórych ważnych parametrów obiektu PublicKeyCredential:
Parametry | Teksty reklam |
Identyfikator utworzonego klucza dostępu zakodowany w formacie Base64URL. Ten identyfikator pomaga przeglądarce określić, czy podczas uwierzytelniania na urządzeniu znajduje się pasujący klucz dostępu. Ta wartość musi być przechowywana w bazie danych na backendzie. | |
| |
Obiekt | |
Zakodowany obiekt atestu |
Uruchom aplikację. Będziesz mieć możliwość kliknięcia przycisku Zarejestruj się za pomocą kluczy dostępu i utworzenia klucza dostępu.
4. Zapisywanie hasła u dostawcy danych logowania
W tej aplikacji na ekranie rejestracji masz już zaimplementowaną rejestrację przy użyciu nazwy użytkownika i hasła.
Aby zapisać dane logowania użytkownika u dostawcy haseł, musisz wdrożyć CreatePasswordRequest, aby przekazać je do createCredential() i zapisać hasło.
- Znajdź metodę
signUpWithPassword()i zastąp komentarz TODO wywołaniemcreatePassword:
SignUpViewModel.kt
TODO("CreatePasswordRequest with entered username and password")
val passwordRequest = CreatePasswordRequest(_username.value, _password.value)
- Następnie utwórz dane logowania za pomocą żądania utworzenia hasła i zapisz dane logowania użytkownika u dostawcy haseł. Następnie zaloguj użytkownika. Wyjątki występujące w tym procesie są wykrywane w bardziej ogólny sposób. Zastąp komentarz TODO tym kodem:
SignUpViewModel.kt
TODO("Create credential with created password request and log the user in")
try {
createCredential(passwordRequest)
simulateServerDelayAndLogIn()
} catch (e: Exception) {
val errorMessage = "Exception Message : " + e.message
Log.e("Auth", errorMessage)
_passwordCreationError.value = errorMessage
_isLoading.value = false
}
Hasło zostało zapisane u dostawcy haseł użytkownika, dzięki czemu możesz uwierzytelniać się za pomocą hasła jednym kliknięciem.
5. Dodawanie możliwości uwierzytelniania za pomocą klucza dostępu lub hasła
Możesz teraz używać go jako bezpiecznego sposobu uwierzytelniania w aplikacji.

Zdefiniuj funkcję lambda getCredential() do użycia w modelach widoku
Podobnie jak wcześniej, wywołamy getCredential() Credential Manager w osobnym pliku CredentialManagerUtil.kt, aby odwoływać się do niego na odpowiednich ekranach i przekazywać go do modeli widoku jako wywołania zwrotne za pomocą funkcji lambda.
Znajdź komentarz TODO w funkcji getCredential() w pliku CredentialManagerUtil.kt i wywołaj funkcję CredentialManager.get():
suspend fun getCredential(
activity: Activity,
request: GetCredentialRequest
): GetCredentialResponse {
TODO("Create a CredentialManager object and call getCredential() with a GetCredentialRequest")
val credentialManager = CredentialManager.create(activity)
return credentialManager.getCredential(activity, request)
}
Uzyskaj wyzwanie i inne opcje, które należy przekazać do wywołania getPasskey()
Zanim poprosisz użytkownika o uwierzytelnienie, musisz poprosić serwer o przekazanie parametrów do przekazania w formacie JSON WebAuthn, w tym wyzwania.
W zasobach masz już odpowiedź symulacyjną (AuthFromServer.txt), która zwraca takie parametry w tym laboratorium.
- W aplikacji otwórz plik SignInViewModel.kt i znajdź metodę
signInWithSavedCredentials, w której napiszesz logikę uwierzytelniania za pomocą zapisanego klucza dostępu lub hasła i umożliwisz użytkownikowi zalogowanie się: - Utwórz funkcję GetPublicKeyCredentialOption() z niezbędnymi parametrami wymaganymi do uzyskania danych logowania od dostawcy danych logowania.
SignInViewModel.kt
TODO("Create a GetPublicKeyCredentialOption() with necessary authentication json from server")
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(jsonProvider.fetchAuthJson(), null)
Metoda fetchAuthJsonFromServer() odczytuje odpowiedź JSON uwierzytelniania z zasobów i zwraca JSON uwierzytelniania, aby pobrać wszystkie klucze dostępu powiązane z tym kontem użytkownika.
Drugi parametr funkcji GetPublicKeyCredentialOption() to clientDataHash – skrót używany do weryfikacji tożsamości podmiotu polegającego na uwierzytelnianiu. Ustaw tę wartość tylko wtedy, gdy ustawisz element GetCredentialRequest.origin. W przypadku przykładowej aplikacji jest to null.
Uwaga : serwer w tym module został zaprojektowany tak, aby zwracać kod JSON jak najbardziej podobny do słownika PublicKeyCredentialRequestOptions przekazywanego do wywołania getCredential() interfejsu API. Poniższy fragment kodu zawiera kilka przykładowych opcji, które możesz otrzymać w rzeczywistej odpowiedzi:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
W tabeli poniżej znajdziesz opis niektórych ważnych parametrów obiektu PublicKeyCredentialRequestOptions:
Parametry | Teksty reklam |
Wygenerowane przez serwer wyzwanie w obiekcie | |
Identyfikator RP to domena. Witryna może określić swoją domenę lub sufiks, który można zarejestrować. Ta wartość musi być zgodna z parametrem |
- Następnie musisz utworzyć obiekt
PasswordOption(), aby pobrać wszystkie zapisane hasła przechowywane u dostawcy haseł za pomocą interfejsu Credential Manager API dla tego konta użytkownika. W metodziegetSavedCredentials()znajdź komentarz TODO i zastąp go tym kodem:
SigninViewModel.kt
TODO("Create a PasswordOption to retrieve all the associated user's password")
val getPasswordOption = GetPasswordOption()
Połącz je w GetCredentialRequest.
SigninViewModel.kt
TODO("Combine requests into a GetCredentialRequest")
val request = GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
Uzyskaj dane logowania
Następnie musisz wywołać getCredential() request ze wszystkimi powyższymi opcjami, aby pobrać powiązane dane logowania:
SignInViewModel.kt
try {
TODO("Call getCredential() with required credential options")
val result = getCredential(request)
val data = when (result.credential) {
is PublicKeyCredential -> {
val cred = result.credential as PublicKeyCredential
DataProvider.setSignedInThroughPasskeys(true)
"Passkey: ${cred.authenticationResponseJson}"
}
is PasswordCredential -> {
val cred = result.credential as PasswordCredential
DataProvider.setSignedInThroughPasskeys(false)
"Got Password - User:${cred.id} Password: ${cred.password}"
}
is CustomCredential -> {
//If you are also using any external sign-in libraries, parse them here with the utility functions provided.
null
}
else -> null
}
TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
} catch (e: Exception) {
Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
_signInError.value =
"An error occurred while authenticating: " + e.message.toString()
} finally {
_isLoading.value = false
}
- Przekazujesz wymagane informacje do
getCredential(). Pobiera listę opcji danych logowania i kontekst aktywności, aby renderować opcje w arkuszu u dołu w tym kontekście. - Po pomyślnym przesłaniu prośby na ekranie pojawi się arkusz z listą wszystkich utworzonych danych logowania do powiązanego konta.
- Użytkownicy mogą teraz potwierdzać swoją tożsamość za pomocą danych biometrycznych lub blokady ekranu, aby uwierzytelnić wybrane dane logowania.
- Jeśli wybrane dane logowania to
PublicKeyCredential, ustaw flagęsetSignedInThroughPasskeysnatrue. W przeciwnym razie ustaw wartośćfalse.
Ten fragment kodu zawiera przykładowy obiekt PublicKeyCredential:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w obiekcie PublicKeyCredential:
Parametry | Teksty reklam |
Identyfikator uwierzytelnionych danych logowania klucza dostępu zakodowany w formacie Base64URL. | |
| |
Obiekt | |
Obiekt | |
Obiekt | |
Obiekt |
- Na koniec musisz przejść proces uwierzytelniania. Zwykle po zakończeniu uwierzytelniania za pomocą klucza dostępu aplikacja wysyła do serwera dane logowania klucza publicznego zawierające potwierdzenie uwierzytelniania. Serwer weryfikuje potwierdzenie i uwierzytelnia użytkownika.
Użyliśmy tu serwera testowego, więc zwracamy tylko true, co oznacza, że serwer zweryfikował potwierdzenie. Więcej informacji o uwierzytelnianiu kluczy dostępu po stronie serwera znajdziesz w dokumentacji.
W metodzie signInWithSavedCredentials() znajdź odpowiedni komentarz i zastąp go tym kodem:
SignInViewModel.kt
TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
if (data != null) {
sendSignInResponseToServer()
_navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = DataProvider.isSignedInThroughPasskeys()))
}
sendSigninResponseToServer()zwraca wartość „true”, co oznacza, że serwer (mock) zweryfikował klucz publiczny do przyszłego użycia.- Po zalogowaniu przekierowujesz użytkownika na ekran główny.
Uruchom aplikację i kliknij Zaloguj się > Zaloguj się za pomocą kluczy dostępu lub zapisanego hasła,a następnie spróbuj zalogować się za pomocą zapisanych danych logowania.
Wypróbuj
W aplikacji na Androida zaimplementowano tworzenie kluczy dostępu, zapisywanie haseł w Menedżerze danych logowania oraz uwierzytelnianie za pomocą kluczy dostępu lub zapisanych haseł przy użyciu interfejsu Credential Manager API.
6. Gratulacje!
To ćwiczenie zostało ukończone. Jeśli chcesz sprawdzić ostateczne rozwiązanie, które jest dostępne na stronie https://github.com/android/identity-samples/tree/main/CredentialManager
Jeśli masz pytania, zadaj je na StackOverflow, dodając tag passkey.