封鎖商店

許多使用者在設定新的 Android 裝置時,仍會自行管理憑證。這種人工作業可能會變得困難,且經常導致使用者體驗不佳。Block Store API 是採用 Google Play 服務的程式庫,可讓應用程式以簡單的方式儲存使用者憑證,避免與儲存使用者密碼相關的複雜性或安全風險。

Block Store API 可讓應用程式儲存資料,以便日後擷取資料,在新裝置上重新驗證使用者。這樣一來,使用者在新的裝置上首次啟動應用程式時,就不會看到登入畫面,可提供更流暢的使用體驗。

使用 Block Store 的好處包括:

  • 開發人員適用的加密憑證儲存空間解決方案。憑證會盡可能採用端對端加密機制。
  • 儲存權杖,而非使用者名稱和密碼。
  • 消除登入流程中的摩擦點。
  • 讓使用者不必管理複雜的密碼。
  • Google 會驗證使用者的身分。

事前準備

如要讓應用程式做好準備,請完成下列各節的步驟。

設定應用程式

在專案層級的 build.gradle 檔案中,請同時在 buildscriptallprojects 區段中納入 Google Maven 存放區

buildscript {
  repositories {
    google()
    mavenCentral()
  }
}

allprojects {
  repositories {
    google()
    mavenCentral()
  }
}

將 Block Store API 的 Google Play 服務依附元件新增至模組的 Gradle 版本檔案,通常為 app/build.gradle

dependencies {
  implementation 'com.google.android.gms:play-services-auth-blockstore:16.4.0'
}

運作方式

開發人員可透過 Block Store 儲存及還原最多 16 位元組陣列。這可讓您儲存目前使用者工作階段的重要資訊,並提供彈性,讓您可以自由儲存這類資訊。這類資料可進行端對端加密,且支援區塊儲存空間的基礎架構會建構在備份與還原基礎架構之上。

本指南將說明將使用者權杖儲存至 Block Store 的用途。以下步驟概述使用 Block Store 的應用程式如何運作:

  1. 您可以在應用程式的驗證流程中,或之後的任何時間,將使用者的驗證權杖儲存到 Block Store,以便日後擷取。
  2. 權杖會儲存在本機,也可以備份到雲端,並盡可能進行端對端加密。
  3. 使用者在新裝置上啟動還原流程時,系統會轉移資料。
  4. 如果使用者在還原流程中還原應用程式,您的應用程式就能從新裝置上的 Block Store 擷取已儲存的符記。

儲存權杖

當使用者登入應用程式時,您可以將為該使用者產生的驗證權杖儲存至 Block Store。您可以使用不重複的鍵值組合值儲存此符記,每個項目的大小上限為 4 KB。如要儲存權杖,請在 StoreBytesData.Builder 的例項上呼叫 setBytes()setKey(),將使用者的憑證儲存至來源裝置。使用 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));

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

使用預設符記

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

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

擷取符記

之後,當使用者在新裝置上執行還原流程時,Google Play 服務會先驗證使用者,然後擷取 Block Store 資料。使用者已同意在還原流程中還原應用程式資料,因此不需要額外同意。當使用者開啟您的應用程式時,您可以呼叫 retrieveBytes(),向 Block Store 要求權杖。接著,系統就能使用擷取到的權杖,讓使用者繼續在新裝置上登入。

以下範例說明如何根據特定鍵值擷取多個符記。

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

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

正在擷取所有符記。

以下範例說明如何擷取儲存在 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));

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

以下範例說明如何擷取預設鍵。

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)

刪除權杖

您可能需要出於下列原因,從 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)

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)

刪除所有權杖

以下範例說明如何刪除目前儲存在 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()
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 物件。當 setShouldBackupToCloud() 設為 true 時,Block Store 會定期將儲存的位元組備份至雲端。

以下範例說明如何僅在雲端備份採用端對端加密技術時啟用雲端備份功能

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. 將 Block Store API 整合至測試應用程式。
  2. 使用測試應用程式叫用 Block Store API 來儲存資料。
  3. 解除安裝測試應用程式,然後在同一裝置上重新安裝應用程式。
  4. 使用測試應用程式叫用 Block Store API 來擷取資料。
  5. 確認擷取的位元組與解除安裝前儲存的位元組相同。

裝置對裝置

在大多數情況下,您必須將目標裝置恢復原廠設定。接著,您可以進入 Android 無線還原流程Google 傳輸線還原流程 (適用於支援的裝置)。

雲端還原

  1. 將 Block Store API 整合至測試應用程式。測試應用程式需要提交至 Play 商店。
  2. 在來源裝置上,使用測試應用程式叫用 Block Store API 來儲存資料,並將 shouldBackUpToCloud 設為 true
  3. 針對 O 以上版本的裝置,您可以手動觸發 Block Store 雲端備份:依序前往「設定」>「Google」>「備份」,然後按一下「立即備份」按鈕。
    1. 如要確認 Block Store 雲端備份是否成功,您可以:
      1. 備份完成後,請搜尋標記為「CloudSyncBpTkSvc」的記錄行。
      2. 您應該會看到類似下列的內容:「......, CloudSyncBpTkSvc: sync result: SUCCESS, ..., uploaded size: XXX bytes ...」
    2. 完成 Block Store 雲端備份後,系統會進行 5 分鐘的「冷卻」期。在 5 分鐘內,按一下「立即備份」按鈕不會觸發另一次 Block Store 雲端備份。
  4. 將目標裝置恢復原廠設定,並完成雲端還原流程。選取即可在還原流程中還原測試應用程式。如要進一步瞭解雲端還原流程,請參閱「支援的雲端還原流程」。
  5. 在目標裝置上,使用測試應用程式叫用 Block store API 來擷取資料。
  6. 確認擷取的位元組與來源裝置中儲存的位元組相同。

裝置需求

端對端加密

  • 搭載 Android 9 (API 29) 以上版本的裝置支援端對端加密功能。
  • 裝置必須設定螢幕鎖定 PIN 碼、解鎖圖案或密碼,才能啟用端對端加密功能,並正確加密使用者的資料。

裝置對裝置還原流程

裝置間還原功能需要來源裝置和目標裝置。這兩部裝置會傳輸資料。

來源裝置必須搭載 Android 6 (API 23) 以上版本才能備份。

指定搭載 Android 9 (API 29) 以上版本的裝置,以便進行還原。

如要進一步瞭解裝置間還原流程,請參閱這篇文章

雲端備份與還原流程

雲端備份與還原功能需要來源裝置和目標裝置。

來源裝置必須搭載 Android 6 (API 23) 以上版本才能備份。

Target 裝置支援的供應商取決於裝置本身。Pixel 裝置可從 Android 9 (API 29) 開始使用這項功能,而所有其他裝置則必須搭載 Android 12 (API 31) 以上版本。