De nombreux utilisateurs gèrent toujours leurs propres identifiants lors de la configuration d'un nouvel appareil Android. Ce processus manuel peut devenir difficile et nuit souvent à l'expérience utilisateur. L'API Block Store, une bibliothèque optimisée par les services Google Play, cherche à résoudre ce problème en permettant aux applications d'enregistrer les identifiants des utilisateurs sans la complexité ni les risques de sécurité associés à l'enregistrement des mots de passe.
L'API Block Store permet à votre application de stocker des données qu'elle peut récupérer ultérieurement pour authentifier à nouveau les utilisateurs sur un nouvel appareil. Cela permet d'offrir une expérience plus fluide à l'utilisateur, car il n'a pas besoin de voir un écran de connexion lorsqu'il lance votre application pour la première fois sur le nouvel appareil.
L'utilisation de Block Store présente plusieurs avantages:
- Solution chiffrée de stockage d'identifiants pour les développeurs Les identifiants sont chiffrés de bout en bout lorsque cela est possible.
- Enregistrer des jetons au lieu de noms d'utilisateur et de mots de passe
- Éliminez les obstacles des flux de connexion.
- Cela leur évite de gérer des mots de passe complexes.
- Google valide l'identité des utilisateurs.
Avant de commencer
Pour préparer votre application, procédez comme indiqué dans les sections suivantes.
Configurer votre application
Dans le fichier build.gradle
au niveau du projet, incluez le dépôt Maven de Google dans les sections buildscript
et allprojects
:
buildscript {
repositories {
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
Ajoutez la dépendance des services Google Play pour l'API Block Store au fichier de compilation Gradle de votre module, qui est généralement app/build.gradle
:
dependencies {
implementation 'com.google.android.gms:play-services-auth-blockstore:16.2.0'
}
Fonctionnement
La plate-forme de stockage de blocs permet aux développeurs d'enregistrer et de restaurer des tableaux de 16 octets maximum. Cela vous permet d'enregistrer des informations importantes sur la session utilisateur en cours et d'enregistrer ces informations comme vous le souhaitez. Ces données peuvent être chiffrées de bout en bout, et l'infrastructure compatible avec Block Store repose sur l'infrastructure de sauvegarde et de restauration.
Ce guide explique comment enregistrer le jeton d'un utilisateur dans le Block Store. Pour suivre le fonctionnement d'une application utilisant le Block Store, procédez comme suit:
- Lors du flux d'authentification de votre application ou à tout moment par la suite, vous pouvez stocker le jeton d'authentification de l'utilisateur dans le Block Store pour le récupérer ultérieurement.
- Le jeton est stocké localement et peut également être sauvegardé dans le cloud, avec un chiffrement de bout en bout lorsque cela est possible.
- Les données sont transférées lorsque l'utilisateur lance un flux de restauration sur un nouvel appareil.
- Si l'utilisateur restaure votre application pendant le processus de restauration, celle-ci peut récupérer le jeton enregistré dans le Block Store sur le nouvel appareil.
Enregistrer le jeton
Lorsqu'un utilisateur se connecte à votre application, vous pouvez enregistrer le jeton d'authentification que vous avez généré pour cet utilisateur dans le Block Store. Vous pouvez stocker ce jeton à l'aide d'une valeur de paire de clés unique de 4 Ko maximum par entrée.
Pour stocker le jeton, appelez setBytes()
et 'setKey(/android/reference/com/google/android/gms/auth/blockstore/StoreBytesData.Builder.html#setKey(java.lang.String)' sur une instance de StoreBytesData.Builder
afin de stocker les identifiants de l'utilisateur sur l'appareil source. Une fois le jeton enregistré avec le stockage de blocs, le jeton est chiffré et stocké localement sur l'appareil.
L'exemple suivant montre comment enregistrer le jeton d'authentification sur l'appareil local:
Java
BlockstoreClient client = Blockstore.getClient(this); byte[] bytes1 = new byte[] { 1, 2, 3, 4 }; // Store one data block. String key1 = "com.example.app.key1"; StoreBytesData storeRequest1 = StoreBytesData.Builder() .setBytes(bytes1) // Call this method to set the key value pair the data should be associated with. .setKeys(Arrays.asList(key1)) .build(); client.storeBytes(storeRequest1) .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes")) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) val bytes1 = byteArrayOf(1, 2, 3, 4) // Store one data block. val key1 = "com.example.app.key1" val storeRequest1 = StoreBytesData.Builder() .setBytes(bytes1) // Call this method to set the key value with which the data should be associated with. .setKeys(Arrays.asList(key1)) .build() client.storeBytes(storeRequest1) .addOnSuccessListener { result: Int -> Log.d(TAG, "Stored $result bytes") } .addOnFailureListener { e -> Log.e(TAG, "Failed to store bytes", e) }
Utiliser le jeton par défaut
Les données enregistrées à l'aide de StoreBytes sans clé utilisent la clé BlockstoreClient.DEFAULT_BYTES_DATA_KEY par défaut.
Java
BlockstoreClient client = Blockstore.getClient(this); // The default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY. byte[] bytes = new byte[] { 9, 10 }; StoreBytesData storeRequest = StoreBytesData.Builder() .setBytes(bytes) .build(); client.storeBytes(storeRequest) .addOnSuccessListener(result -> Log.d(TAG, "stored " + result + " bytes")) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this); // the default key BlockstoreClient.DEFAULT_BYTES_DATA_KEY. val bytes = byteArrayOf(1, 2, 3, 4) val storeRequest = StoreBytesData.Builder() .setBytes(bytes) .build(); client.storeBytes(storeRequest) .addOnSuccessListener { result: Int -> Log.d(TAG, "stored $result bytes") } .addOnFailureListener { e -> Log.e(TAG, "Failed to store bytes", e) }
Récupérer le jeton
Par la suite, lorsqu'un utilisateur effectuera le processus de restauration sur un nouvel appareil, les services Google Play valideront d'abord l'utilisateur, puis récupéreront vos données Block Store. L'utilisateur a déjà accepté de restaurer les données de votre application dans le cadre du flux de restauration. Aucun consentement supplémentaire n'est donc nécessaire. Lorsque l'utilisateur ouvre votre application, vous pouvez demander votre jeton au Block Store en appelant retrieveBytes()
.
Le jeton récupéré peut ensuite être utilisé pour que l'utilisateur reste connecté sur le nouvel appareil.
L'exemple suivant montre comment récupérer plusieurs jetons en fonction de clés spécifiques.
Java
BlockstoreClient client = Blockstore.getClient(this); // Retrieve data associated with certain keys. String key1 = "com.example.app.key1"; String key2 = "com.example.app.key2"; String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to retrieve data stored without a key ListrequestedKeys = Arrays.asList(key1, key2, key3); // Add keys to array RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setKeys(requestedKeys) .build(); client.retrieveBytes(retrieveRequest) .addOnSuccessListener( result -> { Map blockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry entry : blockstoreDataMap.entrySet()) { Log.d(TAG, String.format( "Retrieved bytes %s associated with key %s.", new String(entry.getValue().getBytes()), entry.getKey())); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) // Retrieve data associated with certain keys. val key1 = "com.example.app.key1" val key2 = "com.example.app.key2" val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array val retrieveRequest = RetrieveBytesRequest.Builder() .setKeys(requestedKeys) .build() client.retrieveBytes(retrieveRequest) .addOnSuccessListener { result: RetrieveBytesResponse -> val blockstoreDataMap = result.blockstoreDataMap for ((key, value) in blockstoreDataMap) { Log.d(ContentValues.TAG, String.format( "Retrieved bytes %s associated with key %s.", String(value.bytes), key)) } } .addOnFailureListener { e: Exception? -> Log.e(ContentValues.TAG, "Failed to store bytes", e) }
Récupération de tous les jetons.
Vous trouverez ci-dessous un exemple de récupération de tous les jetons enregistrés dans BlockStore.
Java
BlockstoreClient client = Blockstore.getClient(this) // Retrieve all data. RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setRetrieveAll(true) .build(); client.retrieveBytes(retrieveRequest) .addOnSuccessListener( result -> { MapblockstoreDataMap = result.getBlockstoreDataMap(); for (Map.Entry entry : blockstoreDataMap.entrySet()) { Log.d(TAG, String.format( "Retrieved bytes %s associated with key %s.", new String(entry.getValue().getBytes()), entry.getKey())); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to store bytes", e));
Kotlin
val client = Blockstore.getClient(this) val retrieveRequest = RetrieveBytesRequest.Builder() .setRetrieveAll(true) .build() client.retrieveBytes(retrieveRequest) .addOnSuccessListener { result: RetrieveBytesResponse -> val blockstoreDataMap = result.blockstoreDataMap for ((key, value) in blockstoreDataMap) { Log.d(ContentValues.TAG, String.format( "Retrieved bytes %s associated with key %s.", String(value.bytes), key)) } } .addOnFailureListener { e: Exception? -> Log.e(ContentValues.TAG, "Failed to store bytes", e) }
Vous trouverez ci-dessous un exemple de récupération de la clé par défaut.
Java
BlockStoreClient client = Blockstore.getClient(this); RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build(); client.retrieveBytes(retrieveRequest);
Kotlin
val client = Blockstore.getClient(this) val retrieveRequest = RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build() client.retrieveBytes(retrieveRequest)
Supprimer des jetons
La suppression de jetons de BlockStore peut être nécessaire pour les raisons suivantes:
- L'utilisateur passe par le parcours utilisateur de déconnexion.
- Le jeton a été révoqué ou n'est pas valide.
Comme pour la récupération de jetons, vous pouvez spécifier les jetons à supprimer en définissant un tableau de clés à supprimer.
Vous trouverez ci-dessous un exemple de suppression de certaines clés.
Java
BlockstoreClient client = Blockstore.getClient(this); // Delete data associated with certain keys. String key1 = "com.example.app.key1"; String key2 = "com.example.app.key2"; String key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY; // Used to delete data stored without key ListrequestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array DeleteBytesRequest deleteRequest = new DeleteBytesRequest.Builder() .setKeys(requestedKeys) .build(); client.deleteBytes(deleteRequest)
Kotlin
val client = Blockstore.getClient(this) // Retrieve data associated with certain keys. val key1 = "com.example.app.key1" val key2 = "com.example.app.key2" val key3 = BlockstoreClient.DEFAULT_BYTES_DATA_KEY // Used to retrieve data stored without a key val requestedKeys = Arrays.asList(key1, key2, key3) // Add keys to array val retrieveRequest = DeleteBytesRequest.Builder() .setKeys(requestedKeys) .build() client.deleteBytes(retrieveRequest)
Supprimer tous les jetons
L'exemple ci-dessous supprime tous les jetons actuellement enregistrés dans BlockStore:
Java
// Delete all data. DeleteBytesRequest deleteAllRequest = new DeleteBytesRequest.Builder() .setDeleteAll(true) .build(); client.deleteBytes(deleteAllRequest) .addOnSuccessListener(result -> Log.d(TAG, "Any data found and deleted? " + result));
Kotlin
val deleteAllRequest = DeleteBytesRequest.Builder() .setDeleteAll(true) .build() client.deleteBytes(deleteAllRequest) .addOnSuccessListener { result: Boolean -> Log.d(TAG, "Any data found and deleted? $result") }
Chiffrement de bout en bout
Pour que le chiffrement de bout en bout soit disponible, l'appareil doit être équipé d'Android 9 ou version ultérieure, et l'utilisateur doit avoir défini un verrouillage de l'écran (code, schéma ou mot de passe) pour son appareil. Vous pouvez vérifier si le chiffrement sera disponible sur l'appareil en appelant isEndToEndEncryptionAvailable()
.
L'exemple suivant montre comment vérifier si le chiffrement sera disponible lors de la sauvegarde dans le cloud:
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { result ->
Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
}
Activer la sauvegarde dans le cloud
Pour activer la sauvegarde dans le cloud, ajoutez la méthode setShouldBackupToCloud()
à votre objet StoreBytesData
. Block Store sauvegarde régulièrement dans le cloud les octets stockés lorsque setShouldBackupToCloud()
est défini sur "true".
L'exemple suivant montre comment activer la sauvegarde dans le cloud uniquement lorsque la sauvegarde dans le cloud est chiffrée de bout en bout:
val client = Blockstore.getClient(this)
val storeBytesDataBuilder = StoreBytesData.Builder()
.setBytes(/* BYTE_ARRAY */)
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { isE2EEAvailable ->
if (isE2EEAvailable) {
storeBytesDataBuilder.setShouldBackupToCloud(true)
Log.d(TAG, "E2EE is available, enable backing up bytes to the cloud.")
client.storeBytes(storeBytesDataBuilder.build())
.addOnSuccessListener { result ->
Log.d(TAG, "stored: ${result.getBytesStored()}")
}.addOnFailureListener { e ->
Log.e(TAG, “Failed to store bytes”, e)
}
} else {
Log.d(TAG, "E2EE is not available, only store bytes for D2D restore.")
}
}
Tests
Utilisez les méthodes suivantes pendant le développement pour tester les flux de restauration.
Désinstaller/Réinstaller le même appareil
Si l'utilisateur active les services de sauvegarde (cela peut être vérifié sous Paramètres > Google > Sauvegarde), les données de la plate-forme de blocage sont conservées d'une désinstallation à l'autre.
Pour ce faire, procédez comme suit:
- Intégrez l'API BlockStore à votre application de test.
- Utilisez l'application de test pour appeler l'API BlockStore afin de stocker vos données.
- Désinstallez votre application de test, puis réinstallez-la sur le même appareil.
- Utilisez l'application de test pour appeler l'API BlockStore afin de récupérer vos données.
- Vérifiez que les octets récupérés sont identiques à ceux stockés avant la désinstallation.
Appareil à appareil
Dans la plupart des cas, vous devrez rétablir la configuration d'usine de l'appareil cible. Vous pouvez ensuite saisir le flux de restauration sans fil Android ou la restauration câblée Google (pour les appareils compatibles).
Restauration dans le cloud
- Intégrez l'API Blockstore à votre application de test, qui doit être envoyée au Play Store.
- Sur l'appareil source, utilisez l'application de test pour appeler l'API Blockstore afin de stocker vos données, avec shouldBackUpToCloud défini sur "true".
- Pour les appareils dotés de la version O ou ultérieure, vous pouvez déclencher manuellement une sauvegarde dans le cloud : accédez à Paramètres > Google > Sauvegarde et cliquez sur le bouton "Sauvegarder maintenant".
- Pour vérifier que la sauvegarde dans le cloud a été correctement configurée, vous pouvez :
- Une fois la sauvegarde terminée, recherchez les lignes de journal contenant le tag "CloudSyncBpTkSvc".
- Vous devriez voir les lignes suivantes : "......, CloudSyncBpTkSvc: sync result: SUCCESS, ..., uploaded size: XXX octets ..."
- Après une sauvegarde dans le cloud, une période de "refroidissement" de cinq minutes s'écoule. Au cours de ces cinq minutes, le clic sur le bouton "Sauvegarder maintenant" ne déclenche pas d'autre sauvegarde dans le cloud.
- Pour vérifier que la sauvegarde dans le cloud a été correctement configurée, vous pouvez :
- Rétablissez la configuration d'usine de l'appareil cible et suivez une procédure de restauration dans le cloud. Sélectionnez cette option pour restaurer votre application de test lors du flux de restauration. Pour en savoir plus sur les flux de restauration dans le cloud, consultez la page Flux de restauration de cloud compatibles.
- Sur l'appareil cible, utilisez l'application de test pour appeler l'API Blockstore afin de récupérer vos données.
- Vérifiez que les octets récupérés sont identiques à ceux stockés dans l'appareil source.
Configuration requise de l'appareil
Chiffrement de bout en bout
- Le chiffrement de bout en bout est compatible avec les appareils équipés d'Android 9 (API 29) ou version ultérieure.
- Le verrouillage de l'écran de l'appareil doit être configuré à l'aide d'un code, d'un schéma ou d'un mot de passe pour que le chiffrement de bout en bout puisse être activé et que les données de l'utilisateur soient chiffrées correctement.
Flux de restauration d'appareil à appareil
La restauration d'un appareil à l'autre nécessite un appareil source et un appareil cible. Ce sont les deux appareils qui transfèrent des données.
Pour effectuer la sauvegarde, les appareils source doivent être équipés d'Android 6 (API 23) ou version ultérieure.
Ciblez les appareils équipés d'Android 9 (API 29) ou version ultérieure pour pouvoir effectuer la restauration.
Pour en savoir plus sur le processus de restauration d'un appareil à un autre, cliquez ici.
Flux de sauvegarde et de restauration dans le cloud
La sauvegarde et la restauration dans le cloud nécessitent un appareil source et un appareil cible.
Pour effectuer la sauvegarde, les appareils source doivent être équipés d'Android 6 (API 23) ou version ultérieure.
Les appareils Target (Cible) sont compatibles avec leurs fournisseurs. Les appareils Pixel peuvent utiliser cette fonctionnalité depuis Android 9 (API 29). Tous les autres appareils doivent être équipés d'Android 12 (API 31) ou version ultérieure.