許多使用者在設定新的 Android 裝置時,仍會自行管理憑證。這項手動程序可能相當困難,而且往往會導致使用者體驗不佳。Block Store API 是採用 Google Play 服務的程式庫,可讓應用程式儲存使用者憑證,避免與儲存使用者密碼相關聯的複雜性或安全風險,進而解決這個問題。
Block Store API 可讓應用程式儲存資料,以便日後擷取資料,在新裝置上重新驗證使用者。這有助於為使用者提供更流暢的體驗,因為他們在新裝置上首次啟動應用程式時,不需要看到登入畫面。
使用 Block Store 的好處包括:
- 開發人員專用的加密憑證儲存空間解決方案。憑證會盡可能經過端對端加密。
- 儲存權杖,而非使用者名稱和密碼。
- 消除登入流程中的阻礙。
- 使用者不必再費心管理複雜的密碼。
- Google 會驗證使用者身分。
事前準備
如要讓應用程式做好準備,請完成下列各節的步驟。
設定應用程式
在專案層級 build.gradle 檔案中,請同時在 buildscript 和 allprojects 區段中納入 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 的應用程式如何運作:
- 在應用程式的驗證流程中或之後,您可以將使用者的驗證權杖儲存至 Block Store,以便日後擷取。
- 權杖會儲存在本機,也可以備份到雲端,並盡可能經過端對端加密。
- 使用者在新裝置上啟動還原流程時,系統就會轉移資料。
- 如果使用者在還原流程中還原應用程式,應用程式就能在新裝置上從 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<String> 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<String> 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)
刪除所有權杖
以下範例說明如何刪除儲存在 Block Store 中的所有權杖:
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
// Delete all data.
val deleteAllRequest = DeleteBytesRequest.Builder()
.setDeleteAll(true)
.build()
client.deleteBytes(deleteAllRequest)
.addOnSuccessListener { result -> 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 物件。如果 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 資料會在應用程式解除安裝及重新安裝後保留。
請按照下列步驟進行測試:
- 將 Block Store API 整合至測試應用程式。
- 使用測試應用程式叫用 Block Store API,儲存資料。
- 解除安裝測試應用程式,然後在同一部裝置上重新安裝應用程式。
- 使用測試應用程式叫用 Block Store API,擷取資料。
- 確認擷取的位元組與解除安裝前儲存的位元組相同。
裝置對裝置
在多數情況下,這需要將目標裝置恢復原廠設定。接著,您可以進入 Android 無線還原流程或 Google 傳輸線還原 (適用於支援的裝置)。
雲端還原
- 將 Block Store API 整合至測試應用程式。測試應用程式必須提交至 Play 商店。
- 在來源裝置上,使用測試應用程式叫用 Block Store API 來儲存資料,並將
shouldBackUpToCloud設為true。 - 在搭載 Android 8.0 (API 級別 26) 以上版本的裝置上,您可以手動觸發 Block Store 雲端備份:依序前往「設定」>「Google」>「備份」,然後按一下「立即備份」按鈕。
- 如要確認 Block Store 雲端備份是否成功,您可以:
- 備份完成後,請搜尋含有
[CloudSyncBackupTaskService]標記的記錄行。 - 您應該會看到類似以下的內容:
..., [CloudSyncBackupTaskService] sync result: SUCCESS,..., uploaded size: [size] bytes ...
- 備份完成後,請搜尋含有
- Block Store 雲端備份完成後,會有 5 分鐘的「冷卻」期。在這 5 分鐘內,點選「立即備份」按鈕不會觸發另一個 Block Store 雲端備份。
- 如要確認 Block Store 雲端備份是否成功,您可以:
- 將目標裝置恢復原廠設定,並完成雲端還原流程。在還原流程中選取此選項,即可還原測試應用程式。如要進一步瞭解雲端還原流程,請參閱「支援的雲端還原流程」。
- 在目標裝置上,使用測試應用程式叫用 Block Store API 擷取資料。
- 確認擷取的位元組與儲存在來源裝置中的位元組相同。
裝置需求
請參閱下列規定,瞭解參與 Block Store 作業的裝置。
端對端加密
- 搭載 Android 9 (API 29) 以上版本的裝置支援端對端加密。
- 裝置必須設定 PIN 碼、解鎖圖案或密碼的螢幕鎖定,才能啟用端對端加密,並正確加密使用者資料。
裝置對裝置還原流程
裝置對裝置還原需要來源裝置和目標裝置。這兩部裝置將用於轉移資料。
來源裝置必須搭載 Android 6 (API 23) 以上版本,才能備份。
指定搭載 Android 9 (API 29) 以上版本的裝置,才能進行還原。
如要進一步瞭解裝置對裝置還原流程,請參閱「支援的裝置對裝置還原流程」。
Cloud 備份與還原流程
雲端備份和還原需要來源裝置和目標裝置。
來源裝置必須搭載 Android 6 (API 23) 以上版本,才能備份。
目標裝置是否支援,取決於供應商。Pixel 裝置可從 Android 9 (API 29) 開始使用這項功能,所有其他裝置則必須搭載 Android 12 (API 31) 以上版本。