Многие пользователи по-прежнему управляют своими учетными данными при настройке нового устройства Android. Этот ручной процесс может оказаться сложным и часто приводит к ухудшению пользовательского опыта. API Block Store, библиотека на базе сервисов Google Play , пытается решить эту проблему, предоставляя приложениям возможность сохранять учетные данные пользователей без сложностей или рисков безопасности, связанных с сохранением паролей пользователей.
API Block Store позволяет вашему приложению хранить данные, которые оно может позже получить для повторной аутентификации пользователей на новом устройстве. Это помогает сделать работу пользователя более удобно��, поскольку ему не нужно видеть экран входа в систему при первом запуске вашего приложения на новом устройстве.
Преимущества использования Block Store включают следующее:
- Зашифрованное решение для хранения учетных данных для разработчиков. Учетные данные по возможности шифруются сквозным шифрованием.
- Сохраняйте токены вместо имен пользователей и паролей.
- Устраните препятствия в процессах входа в систему.
- Избавьте пользователей от бремени управления сложными паролями.
- Google проверяет личность пользователя.
Прежде чем вы начнете
Чтобы подготовить приложение, выполните действия, описанные в следующих разделах.
Настройте свое приложение
В файле build.gradle
уровня проекта включите репозиторий Google Maven как в разделы buildscript
, так и в разделы allprojects
:
buildscript {
repositories {
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
Добавьте зависимость сервисов Google Play для API Block Store в файл сборки Gradle вашего модуля (обычно это app/build.gradle
):
dependencies {
implementation 'com.google.android.gms:play-services-auth-blockstore:16.3.1'
}
Как это работает
Block Store позволяет разработчикам сохранять и восстанавливать массивы размером до 16 байт. Это позволяет вам сохранять важную информацию о текущем сеансе пользователя и дает возможность сохранять эту информацию по своему усмотрению. Эти данные могут быть полностью зашифрованы, а инфраструктура, поддерживающая Block Store, построена по��ерх инфраструктуры резервного копирования и восстановления.
В этом руководстве будет рассмотрен вариант использования токена пользователя в Block Store. Следующие шаги описывают, как будет работать приложение, использующее Block Store:
- Во время процесса аутентификации вашего приложения или в любое время после этого вы можете сохранить токен аутентификации пользователя в Block Store для последующего извлечения.
- Токен будет храниться локально, а также может быть сохранен в облаке со сквозным шифрованием, если это возможно.
- Данные передаются, когда пользователь инициирует процесс восстановления на новом устройстве.
- Если пользователь восстанавливает ваше приложение во время процесса восстановления, ваше приложение сможет получить сохраненный токен из Block Store на новом устройстве.
Сохранение токена
Когда пользователь входит в ваше приложение, вы можете сохранить токен аутентификации, созданный для этого пользователя, в Block Store. Вы можете сохранить этот токен, используя уникальное значение пары ключей, размер каждой записи которого не превышает 4 КБ. Чтобы сохранить токен, вызовите setBytes()
и setKey()
в экземпляре StoreBytesData.Builder
, чтобы сохранить учетные данные пользователя на исходном устройстве. После сохранения токена в Block Store он шифруется и сохраняется локально на устройстве.
В следующем примере показано, как сохранить токен аутентификации на локальном устройстве:
Джава
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));
Котлин
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) }
Использовать токен по умолчанию
Для данных, сохраненных с помощью StoreBytes без ключа, используется ключ по умолчанию BlockstoreClient.DEFAULT_BYTES_DATA_KEY.
Джава
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));
Котлин
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) }
Получение токена
Позже, когда пользователь проходит процесс восстановления на новом устройстве, службы Google Play сначала проверяют пользователя, а затем извлекают данные вашего Block Store. Пользователь уже согласился восстановить данные вашего приложения в рамках процесса восстановления, поэтому никаких дополнительных разрешений не требуется. Когда пользователь открывает ваше приложение, вы можете запросить свой токен из Block Store, вызвав retrieveBytes()
. Полученный токен затем можно использовать для сохранения входа пользователя на новом устройстве.
В следующем примере показано, как получить несколько токенов на основе определенных ключей.
Джава
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));
Котлин
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) }
Получение всех токенов.
Ниже приведен пример того, как получить все токены, сохраненные в BlockStore.
Джава
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));
Котлин
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) }
Ниже приведен пример того, как получить ключ по умолчанию.
Джава
BlockStoreClient client = Blockstore.getClient(this); RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build(); client.retrieveBytes(retrieveRequest);
Котлин
val client = Blockstore.getClient(this) val retrieveRequest = RetrieveBytesRequest.Builder() .setKeys(Arrays.asList(BlockstoreClient.DEFAULT_BYTES_DATA_KEY)) .build() client.retrieveBytes(retrieveRequest)
Удаление токенов
Удаление токенов из BlockStore может потребоваться по следующим причинам:
- Пользователь проходит процедуру выхода из системы.
- Токен был отозван или недействителен.
Подобно получению токенов, вы можете указать, какие токены необходимо удалить, задав массив ключей, которые требуют удаления.
Ниже приведен пример удаления определенных ключей.
Джава
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)
Котлин
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)
Удалить все токены
В приведенном ниже примере удаляются все токены, сохраненные в настоящее время в BlockStore:
Джава
// 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));
Котлин
val deleteAllRequest = DeleteBytesRequest.Builder() .setDeleteAll(true) .build() client.deleteBytes(deleteAllRequest) .addOnSuccessListener { result: Boolean -> Log.d(TAG, "Any data found and deleted? $result") }
Сквозное шифрование
Чтобы обеспечить сквозное шифрование, на устройстве должна быть установлена ОС Android 9 или более поздней версии, а пользователь должен установить блокировку экрана (ПИН-код, графический ключ или пароль) для своего устройства. Вы можете проверить, будет ли шифрование доступно на устройстве, вызвав isEndToEndEncryptionAvailable()
.
В следующем примере показано, как проверить, будет ли доступно шифрование во время резервного копирования в облаке:
client.isEndToEndEncryptionAvailable()
.addOnSuccessListener { result ->
Log.d(TAG, "Will Block Store cloud backup be end-to-end encrypted? $result")
}
Включить резервное копирование в облако
Чтобы включить резервное копирование в облако, добавьте метод setShouldBackupToCloud()
к объекту StoreBytesData
. Block Store будет периодически выполнять резервное копирование в облако сохраненных байтов, если для setShouldBackupToCloud()
установлено значение true.
В следующем примере показано, как включить резервное копирование в облако только в том случае, если резервное копирование в облаке имеет сквозное шифрование :
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.")
}
}
Как протестировать
Используйте следующие методы во время разработки, чтобы протестировать потоки восстановления.
Удаление/переустановка того же устройства
Если пользователь включает службы резервного копирования (это можно проверить в «Настройки» > «Google» > «Резервное копирование» ), данные Block Store сохраняются при удалении/переустановке приложения.
Для проверки вы можете выполнить следующие шаги:
- Интегрируйте API BlockStore в свое тестовое приложение.
- Используйте тестовое приложение, чтобы вызвать API BlockStore для хранения ваших данных.
- Удалите тестовое приложение, а затем переустановите его на том же устройстве.
- Используйте тестовое приложение, чтобы вызвать API BlockStore для получения ваших данных.
- Убедитесь, что полученные байты совпадают с теми, которые были сохранены до удаления.
Устройство к устройству
В большинстве случаев для этого потребуется выполнить сброс настроек целевого устройства до заводских. Затем вы можете войти в процесс восстановления беспроводной сети Android или восстановления с помощью кабеля Google (для поддерживаемых устройств).
Облачное восстановление
- Интегрируйте API Blockstore в свое тестовое приложение. Тестовое приложение необходимо отправить в Play Store.
- На исходном устройстве используйте тестовое приложение, чтобы вызвать API Blockstore для хранения ваших данных, при этом для параметра mustBackUpToCloud установлено значение true.
- Для устройств O и выше вы можете вручную запустить облачное резервное копирование Block Store: перейдите в «Настройки» > «Google» > «Резервное копирование» и нажмите кнопку «Создать резервную копию сейчас».
- Чтобы убедиться, что резервное копирование в облако Block Store прошло успешно, вы можете:
- После завершения резервного копирования найдите строки журнала с тегом CloudSyncBpTkSvc.
- Вы должны увидеть такие строки: «......, CloudSyncBpTkSvc: результат синхронизации: УСПЕХ, ..., загруженный размер: XXX байт...»
- После резервного копирования в облако Block Store следует 5-минутный период «остывания». В течение этих 5 минут нажатие кнопки «Создать резервную копию сейчас» не вызовет еще одну резервную копию облачного хранилища Block Store.
- Чтобы убедиться, что резервное копирование в облако Block Store прошло успешно, вы можете:
- Сбросьте настройки целевого устройства до заводских настроек и выполните процедуру восстановления в облаке. Выберите, чтобы восстановить тестовое приложение во время процесса восстановления. Дополнительные сведения о потоках восстановления в облаке см. в разделе Поддерживаемые потоки восстановления в облаке .
- На целевом устройстве используйте тестовое приложение, чтобы вызвать API Blockstore для получения ваших данных.
- Убедитесь, что полученные байты совпадают с теми, которые были сохранены на исходном устройстве.
Требования к устройству
Сквозное шифрование
- Сквозное шифрование поддерживается на устройствах под управлением Android 9 (API 29) и более поздних версий.
- На устройстве должна быть установлена блокировка экрана с помощью PIN-кода, шаблона или пароля, чтобы можно было включить скв��зное шифрование и правильно зашифровать данные пользователя.
Последовательность восстановления между устройствами
Для восстановления устройства на устройство потребуется наличие исходного устройства и целевого устройства. Это будут два устройства, которые передают данные.
Для резервного копирования исходные устройства должны работать под управлением Android 6 (API 23) или более поздней версии.
Целевые устройства под управлением Android 9 (API 29) и более поздних версий, чтобы иметь возможность восстановления.
Дополнительную информацию о процессе восстановления с одного устройства на другое можно найти здесь .
Процесс резервного копирования и восстановления в облаке
Для резервного копирования и восстановления в облаке потребуются исходное и целевое устройства.
Для резервного копирования исходные устройства должны работать под управлением Android 6 (API 23) или более поздней версии.
Целевые устройства поддерживаются в зависимости от их поставщиков. Устройства Pixel могут использовать эту функцию начиная с Android 9 (API 29), а все остальные устройства должны работать под управлением Android 12 (API 31) или более поздней версии.