封鎖商店

設定新的 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。以下步驟概述使用 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. 如果是 Android 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 商店 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) 以上版本。