Na tej stronie dowiesz się, jak utworzyć klucz przywracania, jak się za jego pomocą zalogować i jak go usunąć.
Umiejętności związane z Androidem
Wyświetl na GitHubiePrzywracanie danych logowania
android skills add restore-credentialsZgodność wersji
Funkcja przywracania danych logowania w Menedżerze danych logowania działa na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub nowszym, Usługami Google Play (GMS) w wersji 242200000 (24.22) lub nowszej oraz biblioteką androidx.credentials w wersji 1.5.0 lub nowszej. Na urządzeniach bez usług Google Play lub ze starszą wersją GMS przechwyć CreateCredentialProviderConfigurationException i GetCredentialProviderConfigurationException, aby aplikacja mogła płynnie przejść do standardowego logowania.
Wymagania wstępne
Skonfiguruj serwer strony zależnej WebAuthn podobny do serwera kluczy dostępu. Jeśli masz już skonfigurowany serwer do obsługi uwierzytelniania za pomocą kluczy dostępu, możesz ponownie użyć tych samych punktów końcowych rejestracji i asercji WebAuthn do kluczy przywracania.
Zależności
Dodaj do pliku build.gradle modułu aplikacji te zależności:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
Interfejs Restore Credentials API jest dostępny w bibliotece androidx.credentials w wersji 1.5.0 i nowszych. Zalecamy jednak używanie najnowszych stabilnych wersji zależności, jeśli to możliwe.
Jak android:allowBackup wpływa na przywracanie danych logowania
Atrybut manifestu android:allowBackup kontroluje tworzenie kopii zapasowej danych aplikacji, która współdziała z interfejsem Restore Credentials na 3 różne sposoby:
- Przywracanie przenoszenia kluczy (niezależnie od
allowBackup): Usługi Google Play przenoszą parę kluczyRestoreCredentialza pomocą w pełni zaszyfrowanej kopii zapasowej w chmurze lub lokalnego przenoszenia z urządzenia na urządzenie, niezależnie od tego, czyandroid:allowBackuptotrueczyfalse. W przypadku przenoszenia danych między urządzeniami klucz przywracania jest zawsze przenoszony. Jedynym wyjątkiem jest sytuacja, gdy użytkownik wyłączył kopię zapasową w chmurze w ustawieniach Androida. - Przywracanie w tle w
BackupAgent(wymagaandroid:allowBackup="true"): ciche logowanie w tle, zanim użytkownik otworzy aplikację na nowym urządzeniu (BackupAgent.onRestoreFinished()), działa tylko wtedy, gdy w pliku manifestu skonfigurowanoandroid:allowBackup="true"iandroid:backupAgent. Jeśliandroid:allowBackup="false", przywróć sesję, gdy użytkownik po raz pierwszy uruchomi głównąActivity. Możesz też wdrożyć oba podejścia, aby zwiększyć niezawodność przywracania. - Testowanie kopii zapasowej w chmurze w Android Studio (wymaga
android:allowBackup="true"): testowanie typu kopii zapasowej w chmurze w narzędziu do tworzenia i przywracania kopii zapasowej w Android Studio wymagaandroid:allowBackup="true", natomiast testowanie między urządzeniami działa niezależnie odallowBackup.
Przegląd
Przywracanie danych uwierzytelniających działa w 3 etapach cyklu życia na poprzednim i nowym urządzeniu użytkownika:
- Utworzenie klucza przywracania na poprzednim urządzeniu: po zalogowaniu się użytkownika (lub przy uruchamianiu aplikacji, jeśli użytkownik jest już zalogowany i w pamięci lokalnej nie ma zapisanego klucza przywracania) pobierz opcje tworzenia z serwera strony zależnej i wywołaj
credentialManager.createCredential(). - Logowanie się za pomocą klucza przywracania na nowym urządzeniu: pobierz przeniesiony klucz
RestoreCredential, stosując strategię dwupoziomową: najpierw w sposób niezauważalny dla użytkownika wBackupAgent.onRestoreFinished()(gdyandroid:allowBackup="true"), aby natychmiast wznowić synchronizację w tle i powiadomienia push, a następnie jako rozwiązanie zastępcze w aktywności menu z aplikacjamiActivity.onCreate()przy pierwszym uruchomieniu aplikacji. - Usuwanie klucza przywracania po wylogowaniu: gdy użytkownik się wyloguje lub jego sesja zostanie unieważniona przez serwer, wywołaj
credentialManager.clearCredentialState()zClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)i wyczyść lokalną flagę synchronizacji.
Tworzenie klucza przywracania
Aplikacja powinna uwzględniać wszystkie przypadki logowania się użytkownika, aby mieć pewność, że aktywni użytkownicy mają utworzony klucz przywracania. Utwórz klucz przywracania w tych sytuacjach:
- Jeśli użytkownik jest zalogowany, a klucz przywracania nie został jeszcze utworzony (np. w metodzie
onCreate()dla głównegoActivity). - gdy użytkownik loguje się lub kończy proces rejestracji nowego konta;
Aby uniknąć wywoływania metody createCredential() przy każdym uruchomieniu aplikacji, zapisz w pamięci lokalnej flagę synchronizacji lokalnej, np. has_synced_restore_credential. Nie przechowuj tej flagi w domyślnej kopii zapasowej SharedPreferences ani DataStore, chyba że plik jest wykluczony w data_extraction_rules.xml. W przeciwnym razie automatyczna kopia zapasowa skopiuje has_synced_restore_credential = true na nowe urządzenie i uniemożliwi mu wygenerowanie lub rotację własnego klucza przywracania.
Tworzenie instancji Menedżera danych logowania
Użyj kontekstu aktywności aplikacji, aby utworzyć instancję obiektu CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
Pobieranie opcji tworzenia danych logowania z serwera aplikacji
Użyj biblioteki zgodnej ze standardem FIDO na serwerze aplikacji, aby wysłać do aplikacji klienckiej kod JSON PublicKeyCredentialCreationOptions wymagany do utworzenia danych logowania do przywracania. Więcej informacji o implementacji po stronie serwera znajdziesz w wytycznych dotyczących strony serwera.
Opcje tworzenia JSON mają taką samą strukturę jak rejestracja klucza dostępu:
{
"challenge": "<base64url-encoded challenge>",
"rp": {
"name": "<relying party name>",
"id": "<relying party host name>"
},
"user": {
"id": "<base64url-encoded user ID>",
"name": "<user name>",
"displayName": "<user display name>"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"attestation": "none",
"excludeCredentials": [
{
"id": "<base64url-encoded credential ID to exclude>",
"type": "public-key"
}
],
"authenticatorSelection": {
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
Upewnij się, że kod JSON opcji tworzenia spełnia te wymagania:
challengeiuser.id: zakoduj obie wartości jako ciągi Base64URL bez dopełnienia (zastępując+znakiem-,/znakiem_i usuwając końcowe znaki=). Standardowe znaki Base64 lub nieprzetworzone identyfikatory liczb całkowitych powodują błądCreateRestoreCredentialDomExceptionlubIllegalArgumentException.rp.id: ustaw domenę strony uzależnionej (na przykładexample.com).pubKeyCredParams: uwzględnij{"type": "public-key", "alg": -7}(ES256) i{"type": "public-key", "alg": -257}(RS256).
Tworzenie klucza przywracania
Po otrzymaniu z serwera pliku JSON z opcjami tworzenia klucza publicznego umieść go w obiekcie CreateRestoreCredentialRequest i wywołaj metodę createCredential() na obiekcie CredentialManager.
// requestJson contains the credential creation options sent by the server
val createRestoreRequest = CreateRestoreCredentialRequest(requestJson)
val response = try {
credentialManager.createCredential(context, createRestoreRequest)
} catch (e: E2eeUnavailableException) {
// Fall back to local device-to-device (D2D) restore if cloud E2EE is unavailable
credentialManager.createCredential(
context,
CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false),
)
}
Najważniejsze informacje o kodzie
Obiekt
CreateRestoreCredentialRequestzawiera te pola:requestJson: opcje tworzenia danych logowania wysyłane przez serwer aplikacji w formacie Web Authentication API dlaPublicKeyCredentialCreationOptionsJSON.isCloudBackupEnabled: poleBoolean, które określa, czy klucz przywracania powinien być zapisywany w kopii zapasowej w chmurze. Domyślnie ta flaga ma wartośćtrue. To pole ma te wartości:true: (zalecane) ta wartość umożliwia tworzenie kopii zapasowej kluczy przywracania w chmurze, jeśli użytkownik ma włączoną kopię zapasową Google i pełne szyfrowanie, np. blokadę ekranu.false: ta wartość powoduje zapisanie klucza lokalnie, a nie w chmurze. Klucz nie jest dostępny na nowym urządzeniu, jeśli użytkownik zdecyduje się na przywrócenie danych z chmury, ale nadal jest przenoszony podczas lokalnej konfiguracji urządzenia z użyciem kabla lub Wi-Fi.
Obsługa odpowiedzi na żądanie utworzenia danych logowania
Interfejs Credential Manager API zwraca odpowiedź typu CreateRestoreCredentialResponse. Ta odpowiedź zawiera odpowiedź na rejestrację danych logowania klucza publicznego w formacie JSON.
Wyślij response.registrationResponseJson z aplikacji na serwer strony zależnej, aby zweryfikować i zapisać klucz publiczny na koncie użytkownika, a następnie zapisz lokalny znacznik synchronizacji w pamięci lokalnej. Więcej informacji o weryfikacji po stronie serwera znajdziesz we wskazówkach dotyczących kluczy dostępu.
Podczas procesu tworzenia klucza przywracania obsługuj te wyjątki:
CreateRestoreCredentialDomException: ten wyjątek występuje, jeśli parametrrequestJsonjest nieprawidłowy i nie jest zgodny z formatem WebAuthn dlaPublicKeyCredentialCreationOptionsJSON.E2eeUnavailableException: ten wyjątek występuje, gdyisCloudBackupEnabledma wartośćtrue, ale urządzenie użytkownika nie ma kopii zapasowej danych ani pełnego szyfrowania, takiego jak blokada ekranu.
Aby mieć pewność, że dane uwierzytelniające do przywracania zostaną utworzone na potrzeby lokalnego przenoszenia danych między urządzeniami, nawet gdy pełne szyfrowanie w chmurze jest niedostępne, przechwyćE2eeUnavailableExceptioni natychmiast ponów próbęcreateCredential()zCreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).IllegalArgumentException: ten wyjątek występuje, jeśli parametrrequestJsonjest pusty, nie jest prawidłowym plikiem JSON lub nie ma prawidłowego parametruuser.idzakodowanego w formacie Base64URL, który jest zgodny ze specyfikacjami WebAuthn.
Logowanie się za pomocą klucza przywracania
Użyj interfejsu Restore Credentials API, aby w sposób niezauważalny dla użytkownika zalogować go podczas procesu konfiguracji urządzenia.
Pobieranie opcji pobierania danych logowania z serwera aplikacji
Zanim wywołasz getCredential() na nowym urządzeniu, poproś serwer aplikacji o wyzwanie JSON WebAuthn PublicKeyCredentialRequestOptions. Ponieważ aplikacja na nowym urządzeniu nie wie jeszcze, który użytkownik się loguje, ten punkt końcowy musi być nieuwierzytelniony (wykrywanie bez użytkownika). Wskazówki dotyczące serwera znajdziesz w artykule Logowanie się za pomocą klucza dostępu i przewodniku po uwierzytelnianiu po stronie serwera.
Plik JSON z opcjami pobierania ma taką strukturę:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
Skonfiguruj plik JSON z opcjami pobierania na serwerze w ten sposób:
allowCredentials: pomiń to pole lub przekaż pustą tablicę ([]), ponieważ konto użytkownika nie jest znane przed przywróceniem. GdygetCredential()zwróci dane, Twój serwer zidentyfikuje użytkownika na podstawie polaresponse.userHandle(które odpowiada wartościuser.idzarejestrowanej podczas tworzenia) i wyszuka klucz publiczny wedługid(credentialId).userVerification: ustaw na"discouraged"(lub"preferred"). Ponieważ funkcja przywracania danych logowania działa w tle bez interfejsu biometrycznego lub blokady ekranu, Android zastępuje"required"wartością"discouraged", a dane uwierzytelniające zwracane na serwer mają flagę User Verified (uv) ustawioną nafalse. Skonfiguruj weryfikator serwera WebAuthn, aby akceptowałuv = falsew przypadku asercji klucza przywracania.
Uzyskiwanie klucza przywracania
Aby uzyskać klucz przywracania na nowym urządzeniu, opakuj requestJson serwera w GetRestoreCredentialOption wewnątrz GetCredentialRequest i wywołaj getCredential() na obiekcie CredentialManager.
Wdróż dwupoziomową strategię przywracania, aby użytkownicy byli zalogowani niezależnie od tego, czy przywracanie danych aplikacji w tle jest uruchomione:
- Poziom 1 (przywracanie w tle w
BackupAgent.onRestoreFinished()): jeśli kopia zapasowa danych aplikacji jest włączona (android:allowBackup="true"), utwórz podklasęBackupAgenti zastąponRestoreFinished()(nie używajonRestore(), która jest wywoływana tylko w przypadku starszych kopii zapasowych klucz-wartość). PonieważonRestoreFinished()jest synchroniczna, agetCredential()to funkcjasuspendw Kotlinie, połącz wywołanie za pomocąrunBlocking(Dispatchers.IO). Ciche przechwytywanie wyjątkuNoCredentialException, gdy nie ma klucza przywracania. - Poziom 2 (rezerwowa usługa działająca na pierwszym planie w programie uruchamiającym
Activity.onCreate()): przy pierwszym uruchomieniu aplikacji, gdy nie ma sesji lokalnej, wywołajgetCredential()zlifecycleScopeprzed wyświetleniem ekranu logowania. Dotyczy to aplikacji zandroid:allowBackup="false"i przywraca stan, jeśli urządzenie było offline, gdy uruchomionoonRestoreFinished().
Jeśli dodasz nowy element BackupAgent do aplikacji, która już korzysta z automatycznej kopii zapasowej z android:allowBackup="true", ustaw też android:fullBackupOnly="true" w elemencie <application> w AndroidManifest.xml, aby Android nadal korzystał z automatycznej kopii zapasowej zamiast przełączać się na starszą kopię zapasową klucz-wartość.
suspend fun getRestoreKey(
fetchAuthenticationJson: () -> String,
) {
// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()
// Create the GetRestoreCredentialOption object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))
try {
val response = credentialManager.getCredential(context, getRequest)
// Type-check and extract the restore credential
val credential = response.credential
if (credential is RestoreCredential) {
val responseJson = credential.authenticationResponseJson
}
} catch (e: NoCredentialException) {
// No restore key is present on this device
}
}
class MyBackupAgent : BackupAgent() {
override fun onRestoreFinished() {
super.onRestoreFinished()
val credentialManager = CredentialManager.create(this)
val restoreFunctions = RestoreCredentialsFunctions(this, credentialManager)
runBlocking(Dispatchers.IO) {
restoreFunctions.getRestoreKey(
fetchAuthenticationJson = {
/* Fetch challenge JSON from server */ ""
}
)
}
}
}
Interfejs Credential Manager API zwraca obiekt GetCredentialResponse, którego właściwość credential to RestoreCredential (a nie PublicKeyCredential) zawierający authenticationResponseJson.
Obsługa odpowiedzi na żądanie logowania
Wyślij restoreCredential.authenticationResponseJson z aplikacji na serwer strony uzależnionej, aby zweryfikować asercję kryptograficzną i wygenerować nowy token sesji dla użytkownika. Szczegółowe informacje o weryfikacji po stronie serwera znajdziesz w artykule Logowanie się za pomocą klucza dostępu.
Podczas projektowania pamięci masowej backendu i zasad cyklu życia kluczy przywracania postępuj zgodnie z tymi wytycznymi:
- Odróżnianie kluczy przywracania od kluczy dostępu użytkownika: oznacz klucze przywracania w bazie danych, aby były ukryte na ekranach zarządzania kluczami dostępu dla użytkowników i nie były objęte weryfikacją użytkownika (
uv = true). - Obsługa wielu aktywnych urządzeń na użytkownika: użytkownik może mieć kilka urządzeń (np. telefon i tablet). Zezwól na wiele aktywnych kluczy przywracania na konto użytkownika (np. klucze według urządzenia lub identyfikatora instalacji) zamiast zastępować pojedynczy wiersz.
- Ustaw odpowiedni czas TTL i usuń osierocone klucze: odinstalowanie aplikacji lub wyczyszczenie danych aplikacji powoduje usunięcie lokalnego klucza przywracania bez powiadamiania serwera. Przechowuj klucze przywracania wystarczająco długo, aby przetrwały okna uaktualnienia urządzenia (nawet jeśli użytkownik wyloguje się na starym urządzeniu po przeniesieniu), i usuwaj nieaktualne klucze na podstawie czasu TTL braku aktywności lub limitów kluczy na użytkownika.
Usuń klucz przywracania
Credential Manager jest bezstanowy i nie ma informacji o aktywności użytkownika, więc nie usuwa automatycznie kluczy przywracania po ich użyciu. Aby usunąć klucz przywracania, wywołaj metodę clearCredentialState(). Ze względów bezpieczeństwa usuwaj klucz za każdym razem, gdy użytkownik się wyloguje, aby nie został on automatycznie zalogowany przy następnym uruchomieniu aplikacji.
Klucze przywracania są usuwane tylko w tych sytuacjach:
- Działania na poziomie systemu: użytkownicy odinstalowują aplikację lub czyszczą jej dane.
- Wywołania na poziomie aplikacji: usuń klucz automatycznie, wywołując funkcję
clearCredentialState()podczas obsługi wylogowywania użytkownika w kodzie aplikacji.
Domyślna wartość ClearCredentialStateRequest() powoduje wyczyszczenie tylko standardowego stanu sesji i pozostawia klucz przywracania bez zmian, dlatego przy wylogowywaniu wykonaj podwójne czyszczenie:
- Wywołaj
credentialManager.clearCredentialState(ClearCredentialStateRequest()), aby wyczyścić stan aktywnej sesji dostawcy danych logowania. - Wywołaj
credentialManager.clearCredentialState()zClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL), aby usunąć przechowywaneRestoreCredential. - Usuń lokalny znacznik
has_synced_restore_credentialz pamięci lokalnej.
// Clear active credential provider session state
credentialManager.clearCredentialState(ClearCredentialStateRequest())
// When the user logs out, delete the restore key
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
val response = credentialManager.clearCredentialState(clearRequest)
Dalsze kroki
- Testowanie funkcji interfejsu Restore Credentials API: testowanie procesów przywracania danych z chmury i z urządzenia na urządzenie za pomocą Android Studio lub
adb shell bmgr. - Zautomatyzowane testowanie tworzenia i przywracania kopii zapasowej: napisz testy hosta JUnit 5 za pomocą
BackupRestoreController, aby zweryfikować tworzenie kopii zapasowejCLOUD_ENCRYPTEDi przywracanieDEVICE_TO_DEVICEw CI.