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

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

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()
client.deleteBytes(deleteAllRequest)
  .addOnSuccessListener { result: Boolean ->
    Log.d(TAG,
          "Any data found and deleted? $result")
  }

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

Чтобы обеспечить сквозное шифрование, на устройстве должна быть установлена ​​ОС Android 9 или более поздней версии, а пользователь должен установить блокировку экрана (PIN-код, графический ключ или пароль) для своего устройства. Вы можете проверить, будет ли шифрование доступно на устройстве, вызвав 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 сохраняются при удалении/переустановке приложения.

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

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

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

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

Облачное восстановление

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