De nombreux utilisateurs gèrent toujours leurs propres identifiants lors de la configuration d'un nouvel appareil Android. Ce processus manuel peut s'avérer difficile et générer souvent une mauvaise expérience utilisateur. L'API Block Store, une bibliothèque fournie 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 le risque 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 offre 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 les avantages suivants:
- Solution de stockage d'identifiants chiffrée pour les développeurs. Dans la mesure du possible, les identifiants sont chiffrés de bout en bout.
- Enregistrez les jetons au lieu des noms d'utilisateur et des mots de passe.
- Éliminez les obstacles liés aux processus de connexion.
- Vous évitez aux utilisateurs de gérer des mots de passe complexes.
- Google valide l'identité de l'utilisateur.
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'
}
Comment ça marche ?
Block Store 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 actuelle et de les enregistrer 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 traite du cas d'utilisation de l'enregistrement du jeton d'un utilisateur dans le Block Store. Les étapes suivantes décrivent le fonctionnement d'une application utilisant Block Store:
- Pendant le 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 sera stocké localement et peut également être sauvegardé dans le cloud, chiffré de bout en bout si possible.
- Les données sont transférées lorsque l'utilisateur lance un processus de restauration sur un nouvel appareil.
- Si l'utilisateur restaure votre application pendant le processus de restauration, celle-ci peut alors récupérer le jeton enregistré dans le Block Store sur le nouvel appareil.
Enregistrer le jeton
Lorsqu'un utilisateur se connecte à votre appli, vous pouvez enregistrer le jeton d'authentification que vous générez pour cet utilisateur dans le Block Store. Vous pouvez stocker ce jeton en utilisant 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 Block Store, il 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
Ensuite, lorsqu'un utilisateur parcourt le flux de restauration sur un nouvel appareil, les services Google Play valident d'abord l'utilisateur, puis récupère vos données Block Store. L'utilisateur a déjà accepté de restaurer les données de votre application dans le cadre du processus de restauration. Aucun consentement supplémentaire n'est donc requis. Lorsque l'utilisateur ouvre votre application, vous pouvez demander votre jeton depuis Block Store en appelant retrieveBytes()
.
Le jeton récupéré peut ensuite être utilisé pour maintenir l'utilisateur 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)
Suppression des jetons
La suppression de jetons du BlockStore peut être requise pour les raisons suivantes:
- L'utilisateur est déconnecté.
- Le jeton a été révoqué ou n'est pas valide.
Comme pour la récupération des 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 qu'un 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 pendant 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.")
}
}