1. Zanim zaczniesz
Tradycyjne rozwiązania do uwierzytelniania wiążą się z wieloma wyzwaniami w zakresie bezpieczeństwa i łatwości obsługi.
Hasła są powszechnie używane, ale...
- Łatwo zapomnieć
- Użytkownicy potrzebują wiedzy do tworzenia silnych haseł.
- Łatwość ataku, zbierania i ponownego wykorzystania przez hakerów.
Android pracował nad utworzeniem interfejsu Credential Manager API, aby uprościć logowanie i eliminować zagrożenia dzięki obsłudze kluczy dostępu – nowej generacji standardu branżowego uwierzytelniania bez hasła.
Menedżer danych logowania łączy obsługę kluczy dostępu z tradycyjnymi metodami uwierzytelniania, takimi jak hasła czy funkcja Zaloguj się przez Google.
Użytkownicy będą mogli tworzyć klucze dostępu i przechowywać je w Menedżerze haseł Google, który synchronizuje te klucze dostępu na urządzeniach z Androidem, na których jest zalogowany użytkownik. Aby użytkownik mógł się zalogować, klucz publiczny musi zostać utworzony i powiązany z kontem użytkownika oraz zapisany na serwerze jego klucz publiczny.
Z tego ćwiczenia w Codelabs dowiesz się, jak rejestrować się przy użyciu kluczy dostępu i hasła za pomocą interfejsu Credential Manager API oraz jak używać ich do uwierzytelniania w przyszłości. Wyróżniamy 2 procesy, w tym:
- Zarejestruj się : za pomocą kluczy dostępu i hasła.
- Logowanie się : za pomocą kluczy dostępu i zapisane hasło.
Wymagania wstępne
- Podstawowa znajomość sposobu uruchamiania aplikacji w Android Studio.
- Podstawowa znajomość procesu uwierzytelniania w aplikacjach na Androida.
- Podstawowa znajomość kluczy 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 z Androidem 9 lub nowszym (w przypadku kluczy dostępu) i Androidem 4.4 lub nowszym(do uwierzytelniania za pomocą hasła przez interfejs Credential Manager API).
- Najlepiej z czujnikiem biometrycznym.
- Pamiętaj, aby zarejestrować urządzenie biometryczne (lub blokadę ekranu).
- Wersja wtyczki Kotlin : 1.8.10
2. Konfiguracja
- Skopiuj na laptopie to repozytorium z gałęzi credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
- Przejdź do modułu CredentialManager i otwórz projekt w Android Studio.
Sprawdzanie początkowego stanu aplikacji
Aby sprawdzić, jak działa początkowy stan aplikacji, wykonaj te czynności:
- Uruchom aplikację.
- Zobaczysz ekran główny z przyciskiem „Zarejestruj się i zaloguj”.
- Możesz kliknąć Zarejestruj się, aby się zarejestrować za pomocą klucza dostępu lub hasła.
- Możesz kliknąć Zaloguj się, aby się zalogować, używając klucza dostępu i zapisane hasło.
Aby dowiedzieć się, czym są klucze dostępu i jak działają, przeczytaj artykuł Jak działają klucze dostępu? .
3. Dodaj możliwość rejestracji za pomocą kluczy dostępu
Podczas rejestrowania nowego konta w aplikacji na Androida, która używa interfejsu Credential Manager API, użytkownicy mogą utworzyć klucz dostępu do swojego konta. Ten klucz dostępu będzie bezpiecznie przechowywany u dostawcy danych uwierzytelniających wybranego użytkownika i używany przy kolejnych logowaniach bez konieczności każdorazowego wprowadzania hasła.
Teraz utworzysz klucz dostępu i zarejestrujesz dane logowania użytkownika za pomocą biometrii lub blokady ekranu.
Zarejestruj się za pomocą klucza dostępu
Inside Credential Manager -> aplikacja -> główny -> java -> SignUpFragment.kt, zobaczysz pole tekstowe „username” i przycisk rejestracji za pomocą klucza dostępu.
Przekaż test zabezpieczający i inną odpowiedź JSON do wywołania createPasskey()
Przed utworzeniem klucza dostępu musisz zażądać od serwera uzyskania niezbędnych informacji, które należy przekazać do interfejsu Credential Manager API podczas wywoływania funkcji createCredential().
Na szczęście masz już w swoich zasobach przykładową odpowiedź(RegFromServer.txt), która zwraca takie parametry w tym ćwiczeniu w Codelabs.
- W aplikacji przejdź do metody SignUpFragment.kt, Find, signUpWithPasskeys, aby zapisać logikę tworzenia klucza dostępu i zezwalania użytkownikowi na dostęp. Tę metodę znajdziesz w tej samej klasie.
- Zaznacz blok Other z komentarzem, aby wywołać funkcję createPasskey() i zastąp ją następującym kodem :
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
Ta metoda zostanie wywołana po wpisaniu na ekranie prawidłowej nazwy użytkownika.
- W metodzie createPasskey() musisz utworzyć funkcję CreatePublicKeyCredentialRequest() ze zwróconymi niezbędnymi parametrami.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
Jest to metoda, która odczytuje odpowiedź JSON rejestracji z zasobów i zwraca plik JSON rejestracji, który zostanie przekazany podczas tworzenia klucza dostępu.
- Znajdź metodę downloadRegistrationJsonFromServer() i zastąp polecenie TODO poniższym kodem, aby zwrócić kod json, a także usunąć instrukcję zwracającą pusty ciąg znaków :
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())
- Znajdziesz tu informacje o rejestracji w pliku JSON z zasobami.
- Ten plik JSON ma 4 pola do zastąpienia.
- Identyfikator UserId musi być unikalny, aby użytkownik mógł utworzyć wiele kluczy dostępu (w razie potrzeby). Zastępujesz identyfikator użytkownika <userId> z wygenerowanym identyfikatorem użytkownika.
- <challenge> musi być unikalny, więc zostanie wygenerowany losowe, unikalne wyzwanie. Metoda jest już w kodzie.
Ten fragment kodu zawiera przykładowe opcje otrzymane z serwera:
{
"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"
}
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w słowniku PublicKeyCredentialCreationOptions
:
Parametry | Teksty reklam |
Wygenerowany przez serwer losowy ciąg o wystarczającej entropii, aby odgadnięcie go było niemożliwe. Powinien mieć co najmniej 16 bajtów. Jest to wymagane, ale nie jest używane podczas rejestracji, chyba że wykonujesz poświadczenie. | |
Unikalny identyfikator użytkownika. Ta wartość nie może zawierać informacji umożliwiających identyfikację, takich jak adresy e-mail czy nazwy użytkowników. Przyda się 16-bajtowa losowa wartość generowana dla każdego konta. | |
To pole powinno zawierać unikalny identyfikator konta rozpoznawany przez użytkownika, np. adres e-mail lub nazwę użytkownika. Będzie to widoczne w selektorze kont. Jeśli używasz nazwy użytkownika, użyj takiej samej wartości jak w przypadku uwierzytelnienia za pomocą hasła. | |
To pole jest opcjonalną, prostszą w użyciu nazwą konta. Jest to łatwa do odczytania przez człowieka nazwa konta użytkownika, przeznaczona tylko do wyświetlania. | |
Podmiot uzależniający odpowiada szczegółowym informacjom zawartym w zgłoszeniu.Potrzebny jest :
| |
Parametry danych uwierzytelniających klucza publicznego to lista dozwolonych algorytmów i typów kluczy. Ta lista musi zawierać co najmniej 1 element. | |
Użytkownik próbujący zarejestrować urządzenie mógł zarejestrować inne urządzenia. Aby ograniczyć tworzenie wielu danych logowania na potrzeby tego samego konta w ramach jednego modułu uwierzytelniającego, możesz zignorować te urządzenia. Element transports (jeśli został podany) powinien zawierać wynik wywołania funkcji getTransports() podczas rejestracji każdego dokumentu. | |
wskazuje, czy urządzenie powinno być zamontowane na platformie i czy nie ma w tym zakresie wymagań. Ustaw wartość „platform”. Oznacza to, że chcemy mieć wbudowany moduł uwierzytelniający, a użytkownik nie będzie pytany o wstawienie np. klucz bezpieczeństwa USB. | |
| mają wartość „wymaganą”. aby utworzyć klucz dostępu. |
Utwórz dane logowania
- Po utworzeniu klasy CreatePublicKeyCredentialRequest() musisz wywołać z utworzonym żądaniem wywołanie createCredential().
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- Musisz przekazać wymagane informacje, aby createCredential().
- Gdy żądanie zostanie zrealizowane, na ekranie pojawi się dolna karta z prośbą o utworzenie klucza dostępu.
- Teraz użytkownicy mogą potwierdzać swoją tożsamość za pomocą biometrii, blokady ekranu itp.
- To Ty zajmujesz się widocznością renderowanych widoków i obsługujesz wyjątki, jeśli żądanie z jakiegoś powodu się nie powiedzie lub nie powiedzie się. W tym miejscu komunikaty o błędach są rejestrowane i wyświetlane w aplikacji w oknie dialogowym błędu. Pełne logi błędów możesz sprawdzić za pomocą Android Studio lub polecenia debugowania adb.
- Teraz należy zakończyć proces rejestracji, wysyłając dane uwierzytelniające klucza publicznego do serwera i wpuszczając użytkownika. Aplikacja odbiera obiekt danych logowania zawierający klucz publiczny, który można wysłać na serwer, aby zarejestrować klucz dostępu.
W tym przypadku użyliśmy pozorowanego serwera, więc zwracamy wartość „true” (prawda), wskazując, że serwer zapisał zarejestrowany klucz publiczny na potrzeby uwierzytelniania i weryfikacji w przyszłości.
W metodzie signUpWithPasskeys() znajdź odpowiedni komentarz i zastąp go następującym kodem:
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()
}
- recordResponse zwracający wartość „true” oznacza, że serwer (pozorny) zapisał klucz publiczny do wykorzystania w przyszłości.
- Flaga setSignedInThroughPasskeys ustawiona na wartość true wskazuje, że logujesz się za pomocą kluczy dostępu.
- Po zalogowaniu się użytkownik zostanie przekierowany na ekran główny.
Następujący fragment kodu zawiera przykładowe opcje, które należy uzyskać:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
Ta tabela nie jest wyczerpująca, ale zawiera ważne parametry z sekcji PublicKeyCredential
:
Parametry | Teksty reklam |
Zakodowany w Base64URL identyfikator utworzonego klucza dostępu. 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 w backendzie. | |
Wersja obiektu identyfikatora danych logowania w | |
Dane klienta zakodowane w obiekcie | |
Obiekt atestu zakodowanego w języku |
Po uruchomieniu aplikacji możesz kliknąć przycisk Zarejestruj się za pomocą kluczy dostępu i utworzyć klucz.
4. Zapisz hasło u dostawcy danych uwierzytelniających
W tej aplikacji na ekranie rejestracji masz już zaimplementowane konto z nazwą użytkownika i hasłem do celów demonstracyjnych.
Aby zapisać dane logowania do hasła użytkownika u dostawcy haseł, wdrożysz funkcję CreatePasswordRequest w celu zapisania hasła w ramach metody createCredential().
- Znajdź metodę signUpWithPassword() zamiennie na wywoływanie funkcji TODO createPassword :
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- W metodzie createPassword() musisz utworzyć takie żądanie dotyczące hasła, a pole TODO należy zastąpić tym kodem :
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- Następnie w metodzie createPassword() musisz utworzyć dane logowania, korzystając z funkcji tworzenia żądania hasła, i zapisać dane logowania użytkownika dotyczące hasła u dostawcy hasła, zastępując pozycję TODO następującym kodem :
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(request, requireActivity()) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- Udało Ci się zapisać dane logowania u dostawcy hasła użytkownika, aby uwierzytelniać za pomocą hasła jednym kliknięciem.
5. Dodaj możliwość uwierzytelniania za pomocą klucza dostępu lub hasła
Teraz możesz go używać do bezpiecznego uwierzytelniania w aplikacji.
Uzyskaj test zabezpieczający i inne opcje przekazania do wywołania getPasskey()
Zanim poprosisz użytkownika o uwierzytelnienie, musisz zażądać parametrów przekazywanych z serwera w postaci kodu json WebAuthn, w tym testu zabezpieczającego.
Masz już w swoich zasobach przykładową odpowiedź(AuthFromServer.txt), która zwraca takie parametry w tym ćwiczeniu w Codelabs.
- W aplikacji przejdź do pliku SignInFragment.kt, znajdź metodę
signInWithSavedCredentials
, w której wpiszesz logikę uwierzytelniania za pomocą zapisanego klucza dostępu lub hasła i pozwolisz użytkownikowi na : - Zaznacz blok Other z komentarzem, aby wywołać funkcję createPasskey() i zastąp ją następującym kodem :
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- W metodzie getSavedCredentials() musisz utworzyć funkcję
GetPublicKeyCredentialOption
() z niezbędnymi parametrami wymaganymi do uzyskania danych logowania od dostawcy danych logowania.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
Jest to metoda, która odczytuje z zasobów odpowiedź JSON uwierzytelniania i zwraca plik JSON uwierzytelniania, aby pobrać wszystkie klucze dostępu powiązane z tym kontem użytkownika.
Drugi parametr : clientDataHash – hasz używany do weryfikacji tożsamości jednostki uzależnionej, ustawiany tylko w przypadku ustawienia metody GetCredentialRequest.origin. W przypadku przykładowej aplikacji wartość null.
Trzeci parametr ma wartość true (prawda), jeśli wolisz, aby operacja zwracała natychmiast, gdy nie ma dostępnych danych logowania, zamiast cofać się do wykrywania zdalnych danych uwierzytelniających. W przeciwnym razie ma wartość „false” (fałsz).
- Znajdź metodę downloadAuthJsonFromServer() i zastąp funkcję TODO poniższym kodem, aby zwrócić kod json, a także usunąć instrukcję zwracającą pusty ciąg znaków :
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
Uwaga : serwer tego ćwiczenia w Codelabs został zaprojektowany do zwracania kodu JSON, który jest jak najbardziej podobny do słownika PublicKeyCredentialRequestOptions
przekazywanego do wywołania getCredential() interfejsu API. Poniżej znajduje się fragment kodu, który zawiera kilka przykładowych opcji, które powinny zostać wyświetlone:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w słowniku PublicKeyCredentialRequestOptions
:
Parametry | Teksty reklam |
Wyzwanie wygenerowane przez serwer w obiekcie | |
Identyfikator RP to domena. Witryna może mieć domenę lub sufiks możliwy do zarejestrowania. Ta wartość musi być zgodna z parametrem |
- Następnie musisz utworzyć obiektPasswordOption(), aby pobierać wszystkie zapisane hasła zapisane u dostawcy haseł przez interfejs Credential Manager API dla tego konta użytkownika. W metodzie getSavedCredentials() znajdź funkcję TODO i zastąp ją następującym :
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
Uzyskaj dane logowania
- Następnie musisz wywołać żądanie getCredential() ze wszystkimi powyższymi opcjami, aby pobrać powiązane dane logowania :
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.
}
- Musisz przekazać wymagane informacje, aby uzyskać dane logowania getCredential(). Spowoduje to wyrenderowanie opcji na dole strony na podstawie listy opcji danych logowania i kontekstu aktywności w danym kontekście.
- Gdy żądanie zostanie zrealizowane, na ekranie pojawi się dolna karta z listą wszystkich utworzonych danych logowania dla powiązanego konta.
- Teraz użytkownicy mogą potwierdzać swoją tożsamość za pomocą biometrii, blokady ekranu itp., aby uwierzytelniać wybrane dane logowania.
- Flaga setSignedInThroughPasskeys ustawiona na wartość true wskazuje, że logujesz się za pomocą kluczy dostępu. W przeciwnym razie ma wartość fałsz.
- To Ty zajmujesz się widocznością renderowanych widoków i obsługujesz wyjątki, jeśli żądanie z jakiegoś powodu się nie powiedzie lub nie powiedzie się. W tym miejscu komunikaty o błędach są rejestrowane i wyświetlane w aplikacji w oknie dialogowym błędu. Pełne logi błędów możesz sprawdzić za pomocą Android Studio lub polecenia debugowania adb.
- Teraz należy zakończyć proces rejestracji, wysyłając dane uwierzytelniające klucza publicznego do serwera i wpuszczając użytkownika. Aplikacja odbiera obiekt danych logowania zawierający klucz publiczny, który można wysłać na serwer w celu uwierzytelnienia za pomocą klucza dostępu.
W tym przypadku użyliśmy pozorowanego serwera, więc zwracamy po prostu wartość „prawda”, wskazując, że serwer zweryfikował klucz publiczny.
W metodzie signInWithSavedCredentials
() znajdź odpowiedni komentarz i zastąp go następującym kodem:
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() zwraca wartość „true” (prawda), wskazując, że serwer (przykładowy) zweryfikował klucz publiczny do wykorzystania w przyszłości.
- Po zalogowaniu się użytkownik zostanie przekierowany na ekran główny.
Ten fragment kodu zawiera przykładowy obiekt PublicKeyCredential
:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
Ta tabela nie jest wyczerpująca, ale zawiera ważne parametry w obiekcie PublicKeyCredential
:
Parametry | Teksty reklam |
Zakodowany w Base64URL identyfikator uwierzytelnionego klucza dostępu. | |
Wersja obiektu identyfikatora danych logowania w | |
Obiekt | |
Obiekt | |
Obiekt | |
Obiekt |
Uruchom aplikację i zaloguj się -> Zaloguj się za pomocą kluczy dostępu lub zapisanego hasła i spróbuj zalogować się przy użyciu zapisanych danych logowania.
Wypróbuj
W aplikacji na Androida udało Ci się wdrożyć tworzenie kluczy dostępu, zapisywanie haseł w Menedżerze danych logowania oraz uwierzytelnianie za pomocą kluczy dostępu lub zapisanych haseł za pomocą interfejsu Credential Manager API.
6. Gratulacje!
Ćwiczenie z programowania ukończone. Ostateczną wersję rozwiązania znajdziesz na stronie https://github.com/android/identity-samples/tree/main/CredentialManager.
Jeśli masz pytania, zadaj je na StackOverflow z tagiem passkey
.