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

Многие пользователи по-прежнему самостоятельно управляют своими учетными данными при настройке нового устройства на базе Android. Этот ручной процесс может стать сложным и часто приводит к неудовлетворительному пользовательскому опыту. Библиотека Block Store API, работающая на базе сервисов 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 он шифруется и хранится локально на устройстве.

В следующем примере показано, как сохранить токен аутентификации на локальном устройстве:

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));

Котлин

  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 .

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));

Котлин

  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() . Полученный токен затем можно использовать для поддержания авторизации пользователя на новом устройстве.

В следующем примере показано, как получить несколько токенов на основе определенных ключей.

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

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.

Java

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)
  }

Ниже приведён пример того, как получить ключ по умолчанию.

Java

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 может потребоваться по следующим причинам:

  • Пользователь проходит процедуру выхода из системы.
  • Токен был отозван или недействителен.

Аналогично извлечению токенов, вы можете указать, какие токены необходимо удалить, задав массив ключей, которые требуют удаления.

Следующий пример демонстрирует, как удалить определенные ключи:

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

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:

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));

Котлин

  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 хранилища блоков и получить ваши данные.
  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) или выше.