חסימת החנות

משתמשים רבים עדיין מנהלים את פרטי הכניסה שלהם בעצמם כשהם מגדירים מכשיר חדש עם Android. התהליך הידני הזה יכול להיות מאתגר, ולעיתים קרובות הוא פוגע בחוויית המשתמש. ‫Block Store API, ספרייה שמופעלת על ידי Google Play Services, נועד לפתור את הבעיה הזו. הוא מאפשר לאפליקציות לשמור את פרטי הכניסה של המשתמשים בלי המורכבות או הסיכון לאבטחה שקשורים לשמירת סיסמאות של משתמשים.

‫Block Store API מאפשר לאפליקציה לאחסן נתונים שאפשר לאחזר בהמשך כדי לאמת מחדש משתמשים במכשיר חדש. כך חוויית המשתמש חלקה יותר, כי לא צריך להציג מסך כניסה כשמפעילים את האפליקציה בפעם הראשונה במכשיר החדש.

היתרונות של השימוש ב-Block Store כוללים:

  • פתרון לאחסון מוצפן של פרטי כניסה למפתחים. פרטי הכניסה מוצפנים מקצה לקצה כשאפשר.
  • שמירת אסימונים במקום שמות משתמשים וסיסמאות.
  • להסיר את החיכוך מתהליכי הכניסה.
  • המשתמשים לא צריכים לנהל סיסמאות מורכבות.
  • ‫Google מאמתת את זהות המשתמש.

לפני שמתחילים

כדי להכין את האפליקציה, מבצעים את השלבים שמפורטים בקטעים הבאים.

הגדרת האפליקציה

בקובץ build.gradle ברמת הפרויקט, צריך לכלול את מאגר ה-Maven של Google בקטעים buildscript ו-allprojects:

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

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

מוסיפים את התלות ב-Google Play Services בשביל Block Store API אל קובץ ה-build של 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 את אסימון האימות שאתם יוצרים בשביל המשתמש הזה. אפשר לאחסן את הטוקן הזה באמצעות ערך ייחודי של זוג מפתחות, בגודל של עד 4KB לכל רשומה. כדי לאחסן את האסימון, צריך להפעיל את 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));

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 Services מאמת קודם את המשתמש ואז מאחזר את הנתונים של 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));

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

הפעלת הגיבוי בענן

כדי להפעיל גיבוי בענן, מוסיפים את ה-method‏ 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 Store API ולאחזר את הנתונים.
  6. מוודאים שהבייטים שאוחזרו זהים לאלה שאוחסנו במכשיר המקור.

דרישות לגבי מכשירים

הצפנה מקצה לקצה

  • הצפנה מקצה לקצה נתמכת במכשירים עם Android מגרסה 9 (API 29) ומעלה.
  • כדי להפעיל הצפנה מקצה לקצה ולהצפין את נתוני המשתמש בצורה נכונה, צריך להגדיר במכשיר נעילת מסך באמצעות קוד אימות, קו פתיחת נעילה או סיסמה.

תהליך השחזור ממכשיר למכשיר

כדי לשחזר ממכשיר למכשיר, צריך מכשיר מקור ומכשיר יעד. אלה יהיו שני המכשירים שביניהם יועברו הנתונים.

כדי לגבות, במכשירי המקור צריכה לפעול מערכת Android מגרסה 6 (API 23) ומעלה.

מטרגטים מכשירים עם Android מגרסה 9 (API 29) ומעלה כדי שתהיה אפשרות לשחזר.

כאן אפשר למצוא מידע נוסף על תהליך השחזור ממכשיר למכשיר.

תהליך הגיבוי והשחזור בענן

כדי לגבות ולשחזר בענן, צריך מכשיר מקור ומכשיר יעד.

כדי לגבות, במכשירי המקור צריכה לפעול מערכת Android מגרסה 6 (API 23) ומעלה.

מכשירי יעד נתמכים על סמך הספקים שלהם. במכשירי Pixel אפשר להשתמש בתכונה הזו החל מ-Android 9 (API 29), ובכל שאר המכשירים צריכה לפעול מערכת Android 12 (API 31) ומעלה.