Questa pagina descrive come creare, accedere ed eliminare una chiave di ripristino.
Competenze Android
Visualizza su GitHubRestore Credentials
android skills add restore-credentialsCompatibilità delle versioni
Restore Credentials di Gestore delle credenziali funziona sui dispositivi con Android 9 (livello API 28) e versioni successive, Google Play Services (GMS) versione core 242200000 (v24.22) o successive e versione 1.5.0 o successive della libreria androidx.credentials. Sui dispositivi senza Google Play Services o con una versione GMS precedente,
intercetta CreateCredentialProviderConfigurationException e
GetCredentialProviderConfigurationException in modo che l'app torni correttamente
all'accesso standard.
Prerequisiti
Configura un server della relying party WebAuthn simile al server per le passkey. Se hai già configurato un server per gestire l'autenticazione con le passkey, puoi riutilizzare gli stessi endpoint di registrazione e asserzione WebAuthn per le chiavi di ripristino.
Dipendenze
Aggiungi le seguenti dipendenze al file build.gradle del modulo dell'app:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Trendy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
Restore Credentials è disponibile dalla versione 1.5.0 e successive della libreria androidx.credentials. Tuttavia, se possibile, ti consigliamo di utilizzare le versioni stabili più recenti delle dipendenze.
In che modo android:allowBackup influisce su Restore Credentials
L'attributo manifest android:allowBackup controlla il backup dei dati dell'app,
che interagisce con Restore Credentials in tre modi distinti:
- Ripristina il trasferimento della chiave (indipendente da
allowBackup): Google Play Services trasferisce la coppia di chiaviRestoreCredentialutilizzando il backup sul cloud con crittografia end-to-end o il trasferimento locale da dispositivo a dispositivo (D2D), indipendentemente dal fatto cheandroid:allowBackupsiatrueofalse. Per i trasferimenti D2D, la chiave di ripristino viene sempre trasferita. L'unica condizione in cui non viene trasferita è durante un backup sul cloud quando l'utente ha disattivato il backup sul cloud nelle Impostazioni di Android. - Ripristino in background in
BackupAgent(richiedeandroid:allowBackup="true"): l'accesso in background silenzioso prima che l'utente apra l'app sul nuovo dispositivo (BackupAgent.onRestoreFinished()) viene eseguito solo quandoandroid:allowBackup="true"eandroid:backupAgentsono configurati nel manifest. Seandroid:allowBackup="false", ripristina la sessione quando l'utente avvia per la prima volta la tuaActivityprincipale. Puoi anche implementare entrambi gli approcci per rendere il ripristino più efficace. - Test del backup sul cloud di Android Studio (richiede
android:allowBackup="true"): il test del tipo di backup Cloud nello strumento di backup e ripristino di Android Studio richiedeandroid:allowBackup="true", mentre il test Da dispositivo a dispositivo funziona indipendentemente daallowBackup.
Panoramica
Restore Credentials segue un ciclo di vita in tre fasi sui dispositivi precedenti e nuovi dell'utente:
- Crea una chiave di ripristino sul dispositivo precedente: dopo che l'utente ha eseguito l'accesso (o all'avvio dell'app se l'utente ha già eseguito l'accesso e non è registrata alcuna chiave di ripristino nello spazio di archiviazione locale), recupera le opzioni di creazione dal server della relying party e chiama
credentialManager.createCredential(). - Accedi con il tasto di ripristino sul nuovo dispositivo: Recupera il trasferito
RestoreCredentialutilizzando una strategia a due livelli: prima silenziosamente inBackupAgent.onRestoreFinished()(Quandoandroid:allowBackup="true") in modo che la sincronizzazione in background e le notifiche push riprendano immediatamente, e in secondo luogo come soluzione di ripiego nel tuo launcherActivity.onCreate()al primo avvio dell'app. - Elimina la chiave di ripristino al momento della disconnessione: quando l'utente si disconnette o la sua sessione viene invalidata dal server, chiama
credentialManager.clearCredentialState()conClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)e cancella il flag di sincronizzazione locale.
Crea una chiave di ripristino
L'app deve coprire tutti i casi di accesso di un utente per garantire che gli utenti attivi abbiano una chiave di ripristino creata. Crea la chiave di ripristino nei seguenti scenari:
- Se l'utente ha eseguito l'accesso e non è già stata creata una chiave di ripristino (ad esempio nel metodo
onCreate()per l'Activityprincipale). - Quando l'utente accede o completa un nuovo flusso di registrazione dell'account.
Per evitare di chiamare createCredential() a ogni avvio dell'app, memorizza un flag di sincronizzazione locale, ad esempio has_synced_restore_credential, nello spazio di archiviazione locale. Non memorizzare questo flag in SharedPreferences o DataStore di cui è stato eseguito il backup per impostazione predefinita, a meno che il file non sia escluso in data_extraction_rules.xml; in caso contrario, il backup automatico copia has_synced_restore_credential = true sul nuovo dispositivo e impedisce a quest'ultimo di generare o ruotare la propria chiave di ripristino.
Crea un'istanza di Gestore delle credenziali
Utilizza il contesto dell'attività della tua app per creare un'istanza di un oggetto CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
Recupera le opzioni di creazione delle credenziali dal server dell'app
Utilizza una libreria conforme a FIDO nel server dell'app per inviare all'app client il codice JSON PublicKeyCredentialCreationOptions richiesto per creare la credenziale di ripristino. Per saperne di più sull'implementazione lato server, consulta le indicazioni lato server.
Il JSON delle opzioni di creazione segue la stessa struttura della registrazione della passkey:
{
"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"
}
}
Assicurati che il JSON delle opzioni di creazione soddisfi questi requisiti:
challengeeuser.id: codifica entrambi i valori come stringhe Base64URL senza spaziatura interna (sostituendo+con-,/con_e rimuovendo=finale). I caratteri Base64 standard o gli ID interi non elaborati causanoCreateRestoreCredentialDomExceptionoIllegalArgumentException.rp.id: imposta il dominio della tua relying party (ad esempio,example.com).pubKeyCredParams: includi{"type": "public-key", "alg": -7}(ES256) e{"type": "public-key", "alg": -257}(RS256).
Crea la chiave di ripristino
Dopo aver ricevuto il JSON delle opzioni di creazione della chiave pubblica dal tuo server, inserisci il JSON in un oggetto CreateRestoreCredentialRequest e chiama createCredential() sull'oggetto CredentialManager.
// createRestoreRequest contains the details sent by the server
val response = credentialManager.createCredential(context, createRestoreRequest)
Punti chiave sul codice
L'oggetto
CreateRestoreCredentialRequestcontiene i seguenti campi:requestJson: le opzioni di creazione delle credenziali inviate dal server dell'app nel formato dell'API Web Authentication perPublicKeyCredentialCreationOptionsJSON.isCloudBackupEnabled: campoBooleanper determinare se eseguire il backup della chiave di ripristino nel cloud. Per impostazione predefinita, questo flag ètrue. Questo campo ha i seguenti valori:true: (consigliato) questo valore attiva il backup delle chiavi di ripristino nel cloud se l'utente ha attivato Google Backup e la crittografia end-to-end, ad esempio un blocco schermo.false: questo valore salva la chiave localmente e non nel cloud. La chiave non è disponibile sul nuovo dispositivo se l'utente sceglie di eseguire il ripristino dal cloud, ma viene comunque trasferita durante una configurazione locale device-to-device (D2D) tramite cavo o Wi-Fi.
Gestisci la risposta alla creazione delle credenziali
L'API Credential Manager restituisce una risposta di tipo CreateRestoreCredentialResponse. Questa risposta contiene la risposta di registrazione delle credenziali della chiave pubblica in formato JSON.
Invia response.registrationResponseJson dalla tua app al server della relying party per verificare e archiviare la chiave pubblica insieme all'account dell'utente, quindi registra l'indicatore di sincronizzazione locale nello spazio di archiviazione locale. Per saperne di più sulla verifica lato server, consulta le indicazioni per le passkey.
Durante il processo di creazione della chiave di ripristino, gestisci queste eccezioni:
CreateRestoreCredentialDomException: questa eccezione si verifica serequestJsonnon è valido e non segue il formato WebAuthn perPublicKeyCredentialCreationOptionsJSON.E2eeUnavailableException: questa eccezione si verifica seisCloudBackupEnabledètrue, ma il dispositivo dell'utente non dispone del backup dei dati o della crittografia end-to-end, ad esempio un blocco schermo.
Per assicurarti che le credenziali di ripristino vengano create per i trasferimenti locali da dispositivo a dispositivo (D2D) anche quando la crittografia end-to-end (E2EE) sul cloud non è disponibile, intercettaE2eeUnavailableExceptione riprova immediatamentecreateCredential()conCreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).IllegalArgumentException: questa eccezione si verifica serequestJsonè vuoto, non è un JSON valido o non ha unuser.idcon codifica Base64URL valido conforme alle specifiche di WebAuthn.
Accedi con una chiave di ripristino
Utilizza Restore Credentials per far accedere l'utente in modo invisibile durante la procedura di configurazione del dispositivo.
Ottieni le opzioni di recupero delle credenziali dal server dell'app
Prima di chiamare getCredential() sul nuovo dispositivo, richiedi una verifica JSON WebAuthn PublicKeyCredentialRequestOptions dal server dell'app. Poiché l'app sul nuovo dispositivo non sa ancora quale utente sta eseguendo l'accesso, questo endpoint deve essere non autenticato (rilevamento senza utente). Per indicazioni relative al server, consulta Accedi con una passkey e la guida all'autenticazione lato server.
Il file JSON delle opzioni di recupero ha la seguente struttura:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
Configura il file JSON delle opzioni di recupero sul tuo server come segue:
allowCredentials: ometti questo campo o passa un array vuoto ([]) perché l'account dell'utente non è noto prima del ripristino. Dopo chegetCredential()restituisce il risultato, il tuo server identifica l'utente dal camporesponse.userHandle(che corrisponde auser.idregistrato durante la creazione) e cerca la chiave pubblica in base aid(credentialId).userVerification: impostato su"discouraged"(o"preferred"). Poiché Restore Credentials viene eseguito in modo silenzioso senza UI biometrica o di blocco schermo, Android esegue l'override di"required"in"discouraged"e i dati dell'autenticatore restituiti al server hanno il flag Utente verificato (uv) impostato sufalse. Configura il verificatore del server WebAuthn in modo che accettiuv = falseper le asserzioni della chiave di ripristino.
Ottieni la chiave di ripristino
Per ottenere la chiave di ripristino sul nuovo dispositivo, inserisci il requestJson del server in un
GetRestoreCredentialOption all'interno di un GetCredentialRequest e chiama
getCredential() sull'oggetto CredentialManager.
Implementa una strategia di ripristino a due livelli in modo che gli utenti possano accedere indipendentemente dall'esecuzione o meno del ripristino dei dati dell'app in background:
- Livello 1 (ripristino in background in
BackupAgent.onRestoreFinished()): se il backup dei dati dell'app è abilitato (android:allowBackup="true"), crea una sottoclasse diBackupAgented esegui l'override dionRestoreFinished()(non utilizzareonRestore(), che viene chiamato solo per i backup di coppie chiave-valore legacy). PoichéonRestoreFinished()è sincrono egetCredential()è una funzionesuspendKotlin, collega la chiamata utilizzandorunBlocking(Dispatchers.IO). IntercettaNoCredentialExceptionsenza problemi quando non è presente alcuna chiave di ripristino. - Livello 2 (fallback in primo piano nell'Avvio app
Activity.onCreate()): al primo avvio dell'app, quando non esiste una sessione locale, chiamagetCredential()dalifecycleScopeprima di mostrare la schermata di accesso. In questo modo vengono gestite le app conandroid:allowBackup="false"e viene eseguito il recupero se il dispositivo era offline quandoonRestoreFinished()è stato eseguito.
Se aggiungi un nuovo BackupAgent a un'app che utilizza già il backup automatico con
android:allowBackup="true", imposta anche android:fullBackupOnly="true" sull'elemento
<application> in AndroidManifest.xml in modo che Android continui a utilizzare
il backup automatico anziché passare al backup di coppie chiave-valore precedente.
// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()
// Create the GetRestoreCredentialRequest object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))
val response = credentialManager.getCredential(context, getRequest)
// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential
L'API Credential Manager restituisce un GetCredentialResponse la cui proprietà credential è un RestoreCredential (non un PublicKeyCredential) contenente authenticationResponseJson.
Gestisci la risposta di accesso
Invia restoreCredential.authenticationResponseJson dall'app al server della relying party per verificare l'asserzione crittografica ed emettere un nuovo token di sessione per l'utente. Per i dettagli sulla verifica lato server, consulta Accedi con una passkey.
Quando progetti lo spazio di archiviazione di backend e le policy del ciclo di vita per le chiavi di ripristino, segui queste linee guida:
- Differenzia le chiavi di ripristino dalle passkey utente: tagga le chiavi di ripristino nel tuo database in modo che siano nascoste dalle schermate di gestione delle passkey rivolte agli utenti ed esenti dai controlli di verifica utente (
uv = true). - Supporta più dispositivi attivi per utente: un utente può avere più dispositivi (ad esempio uno smartphone e un tablet). Consenti più chiavi di ripristino attive per account utente (ad esempio, con chiave per dispositivo o ID installazione) anziché sovrascrivere una singola riga.
- Imposta un TTL appropriato e ripulisci le chiavi orfane: la disinstallazione di un'app o la cancellazione dei dati dell'app elimina la chiave di ripristino locale senza inviare una notifica al server. Conserva le chiavi di ripristino per un periodo di tempo sufficiente a coprire le finestre di upgrade del dispositivo (anche se l'utente esce dal vecchio dispositivo dopo il trasferimento) ed elimina le chiavi obsolete in base al TTL di inattività o ai limiti delle chiavi per utente.
Elimina la chiave di ripristino
Gestore delle credenziali è stateless e non è a conoscenza dell'attività utente, pertanto non elimina automaticamente le chiavi di ripristino dopo l'utilizzo. Per eliminare una chiave di ripristino, chiama il metodo clearCredentialState(). Per motivi di sicurezza, elimina la chiave ogni volta che un utente esce, in modo che non venga eseguito l'accesso silenzioso al successivo avvio dell'app.
Le chiavi di ripristino vengono rimosse solo nelle seguenti situazioni:
- Azioni a livello di sistema: gli utenti disinstallano l'app o ne cancellano i dati.
- Chiamate a livello di app: elimina la chiave in modo programmatico chiamando
clearCredentialState()quando gestisci la disconnessione dell'utente nel codice della tua app.
Poiché un ClearCredentialStateRequest() predefinito cancella solo lo stato della sessione standard e lascia intatta la chiave di ripristino, esegui una doppia cancellazione al momento della disconnessione:
- Chiama
credentialManager.clearCredentialState(ClearCredentialStateRequest())per cancellare lo stato della sessione del provider di credenziali attivo. - Chiama
credentialManager.clearCredentialState()conClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)per eliminare ilRestoreCredentialarchiviato. - Elimina l'indicatore
has_synced_restore_credentiallocale dallo spazio di archiviazione locale.
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
// When the user logs out, delete the restore key
val response = credentialManager.clearCredentialState(clearRequest)
Passaggi successivi
- Testa Restore Credentials: testa i flussi di ripristino da cloud e da dispositivo a dispositivo (D2D) utilizzando Android Studio o
adb shell bmgr. - Test automatici di backup e ripristino: scrivi test host JUnit 5 con
BackupRestoreControllerper verificare i ripristini diCLOUD_ENCRYPTEDeDEVICE_TO_DEVICEin CI.