Блочный магазин

Многие пользователи по-прежнему управляют своими учетными данными при настройке нового устройства на базе Android. Этот ручной процесс может стать сложным и часто приводит к плохому пользовательскому опыту. API Block Store, библиотека на базе сервисов Google Play , пытается решить эту проблему, предоставляя приложениям способ сохранения учетных данных пользователя без сложностей или риска безопасности, связанных с сохранением паролей пользователей.

API Block Store позволяет вашему приложению хранить данные, которые оно может позже извлечь для повторной аутентификации пользователей на новом устройстве. Это помогает обеспечить более плавный опыт для пользователя, поскольку ему не нужно видеть экран входа при первом запуске вашего приложения на новом устройстве.

Преимущества использования Block Store включают в себя следующее:

  • Зашифрованное решение для хранения учетных данных для разработчиков. Учетные данные зашифрованы сквозным образом, когда это возможно.
  • Сохраняйте токены вместо имен пользователей и паролей.
  • Устраните затруднения при входе в систему.
  • Избавьте пользователей от необходимости управлять сложными паролями.
  • Google проверяет личность пользователя.

Прежде чем начать

Чтобы подготовить свое приложение, выполните действия, описанные в следующих разделах.

Настройте свое приложение

В файле build.gradle уровня проекта включите репозиторий Maven от Google в разделы 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.4.0'
}

Как это работает

Block Store позволяет разработчикам сохранять и восстанавливать массивы размером до 16 байт. Это позволяет сохранять важную информацию о текущем сеансе пользователя и обеспечивает гибкость сохранения этой информации любым удобным для вас способом. Эти данные могут быть зашифрованы сквозным способом, а инфраструктура, поддерживающая Block Store, построена поверх инфраструктуры резервного копирования и восстановления.

В этом руководстве будет рассмотрен вариант использования сохранения токена пользователя в Block Store. Следующие шаги описывают, как будет работать приложение, использующее Block Store:

  1. Во время процесса аутентификации вашего приложения или в любое время после этого вы можете сохранить токен аутентификации пользователя в Block Store для последующего извлечения.
  2. Токен будет храниться локально, а также может быть скопирован в облако, при возможности с сквозным шифрованием.
  3. Данные переносятся, когда пользователь инициирует процесс восстановления на новом устройстве.
  4. Если пользователь восстановит ваше приложение во время процесса восстановления, ваше приложение сможет извлечь сохраненный токен из 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

List requestedKeys = Arrays.asList(key1, key2, key3); // Add keys to array
RetrieveBytesRequest retrieveRequest = new RetrieveBytesRequest.Builder()
    .setKeys(requestedKeys)
    .build();

client.retrieveBytes(retrieveRequest)
    .addOnSuccessListener(
        result -> {
          Map<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap();
          for (Map.Entry<String, BlockstoreData> 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 -> {
          Map<String, BlockstoreData> blockstoreDataMap = result.getBlockstoreDataMap();
          for (Map.Entry<String, BlockstoreData> 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

List requestedKeys = 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()
retrieve bytes, the key BlockstoreClient.DEFAULT_BYTES_DATA_KEY can be used
in the RetrieveBytesRequest instance in order to get your saved data

The following example shows how to retrieve the default key.

Java

End-to-end encryption

In order for end-to-end encryption to be made available, the device must be running Android 9 or higher, and the user must have set a screen lock (PIN, pattern, or password) for their device. You can verify if encryption will be available on the device by calling isEndToEndEncryptionAvailable().

The following sample shows how to verify if encryption will be available during cloud backup:

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 сохраняются при удалении/переустановке приложения.

Для проверки вы можете выполнить следующие шаги:

  1. Интегрируйте API Block Store в свое тестовое приложение.
  2. Используйте тестовое приложение, чтобы вызвать API Block Store для хранения ваших данных.
  3. Удалите тестовое приложение, а затем переустановите его на том же устройстве.
  4. Используйте тестовое приложение для вызова API Block Store для извлечения данных.
  5. Убедитесь, что извлеченные байты совпадают с теми, которые были сохранены до удаления.

Устройство-устройство

В большинстве случаев это потребует сброса настроек целевого устройства к заводским настройкам. Затем вы можете войти в процесс восстановления Android по беспроводной сети или восстановления Google по кабелю (для поддерживаемых устройств).

Восстановление в облаке

  1. Интегрируйте API Block Store в свое тестовое приложение. Тестовое приложение необходимо отправить в Play Store.
  2. На исходном устройстве используйте тестовое приложение, чтобы вызвать API Block Store для сохранения данных, при этом для shouldBackUpToCloud должно быть установлено значение true .
  3. Для устройств O и выше вы можете вручную запустить резервное копирование в облаке Block Store: перейдите в Настройки > Google > Резервное копирование , нажмите кнопку «Создать резервную копию сейчас».
    1. Чтобы убедиться в успешности резервного копирования в облаке Block Store, вы можете:
      1. После завершения резервного копирования найдите строки журнала с тегом «CloudSyncBpTkSvc».
      2. Вы должны увидеть строки вроде этой: «......, CloudSyncBpTkSvc: результат синхронизации: УСПЕШНО, ..., загруженный размер: XXX байт ...»
    2. После резервного копирования Block Store в облаке наступает 5-минутный период «остывания». В течение этих 5 минут нажатие кнопки «Сделать резервную копию сейчас» не запустит еще одно резервное копирование Block Store в облаке.
  4. Выполните сброс настроек целевого устройства и пройдите процесс восстановления в облаке. Выберите, чтобы восстановить тестовое приложение во время процесса восстановления. Для получения дополнительной информации о процессах восстановления в облаке см. Поддерживаемые процессы восстановления в облаке .
  5. На целевом устройстве используйте тестовое приложение, чтобы вызвать API Block Store для извлечения данных.
  6. Убедитесь, что полученные байты совпадают с теми, которые были сохранены на исходном устройстве.

Требования к устройству

Сквозное шифрование

  • Сквозное шифрование поддерживается на устройствах под управлением 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) или выше.