בדף הזה מוסבר איך ליצור מפתח שחזור, להיכנס באמצעותו ולמחוק אותו.
מיומנויות ב-Android
הצגת הקוד ב-GitHubשחזור פרטי כניסה
android skills add restore-credentialsתאימות בין גרסאות
התכונה "שחזור פרטי הכניסה" של Credential Manager פועלת במכשירים עם Android מגרסה 9 (רמת API 28) ומעלה, גרסת ליבה 242200000 (גרסה 24.22) ומעלה של Google Play Services (GMS), וגרסה 1.5.0 ומעלה של ספריית androidx.credentials. במכשירים שאין בהם את Google Play Services או שיש בהם גרסה ישנה יותר של GMS, צריך להוסיף את CreateCredentialProviderConfigurationException ו-GetCredentialProviderConfigurationException כדי שהאפליקציה תעבור בצורה חלקה לכניסה רגילה.
דרישות מוקדמות
מגדירים שרת של גורם מסתמך ב-WebAuthn, בדומה לשרת של מפתחות הגישה. אם כבר הגדרתם שרת לטיפול באימות באמצעות מפתחות גישה, אתם יכולים להשתמש באותן נקודות קצה (endpoint) של רישום וטענת נכוֹנוּת (assertion) ב-WebAuthn בשביל מפתחות השחזור.
פניות קשורות
מוסיפים את יחסי התלות הבאים לקובץ build.gradle של מודול האפליקציה:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
התכונה "שחזור פרטי הכניסה" זמינה בגרסה 1.5.0 ואילך של הספרייה androidx.credentials. עם זאת, מומלץ להשתמש בגרסאות היציבות העדכניות ביותר של יחסי התלות, ככל האפשר.
איך android:allowBackup משפיע על שחזור פרטי הכניסה
המאפיין android:allowBackup במניפסט שולט בגיבוי של נתוני האפליקציה, שפועל עם "שחזור פרטי הכניסה" בשלוש דרכים שונות:
- העברת מפתח השחזור (לא תלוי ב-
allowBackup): Google Play Services מעבירים את זוג המפתחות שלRestoreCredentialבאמצעות גיבוי בענן שמוצפן מקצה לקצה או העברה מקומית ממכשיר למכשיר (D2D), בלי קשר לשאלה אםandroid:allowBackupהואtrueאוfalse. בהעברות D2D, מפתח השחזור תמיד מועבר. המצב היחיד שבו הוא לא מועבר הוא במהלך גיבוי בענן, כשהמשתמש ביטל את ההסכמה לגיבוי בענן בהגדרות של Android. - שחזור ברקע ב-
BackupAgent(נדרשandroid:allowBackup="true"): כניסה שקטה ברקע לפני שהמשתמש פותח את האפליקציה במכשיר החדש (BackupAgent.onRestoreFinished()) פועלת רק כש-android:allowBackup="true"ו-android:backupAgentמוגדרים במניפסט. אםandroid:allowBackup="false", צריך לשחזר את הסשן כשהמשתמש מפעיל בפעם הראשונה אתActivityהראשי. אפשר גם להטמיע את שתי הגישות כדי להפוך את השחזור ליציב יותר. - בדיקת גיבוי בענן של Android Studio (נדרש
android:allowBackup="true"): כדי לבדוק את סוג הגיבוי בענן בכלי הגיבוי והשחזור של Android Studio, נדרשandroid:allowBackup="true", ואילו בדיקת ממכשיר למכשיר פועלת ללא קשר ל-allowBackup.
סקירה כללית
התכונה "שחזור פרטי הכניסה" פועלת במחזור חיים של שלושה שלבים במכשיר הקודם ובמכשיר החדש של המשתמש:
- יצירת מפתח שחזור במכשיר הקודם: אחרי שהמשתמש נכנס לחשבון (או כשהאפליקציה מופעלת אם המשתמש כבר נכנס לחשבון ולא נשמר מפתח שחזור באחסון המקומי), מאחזרים את אפשרויות היצירה מהשרת של הצד המסתמך וקוראים ל-
credentialManager.createCredential(). - כניסה באמצעות מפתח השחזור במכשיר החדש: מאחזרים את
RestoreCredentialשהועבר באמצעות אסטרטגיה דו-שלבית: קודם כול באופן שקט ב-BackupAgent.onRestoreFinished()(כאשרandroid:allowBackup="true") כדי שהסנכרון ברקע וההתראות יימשכו באופן מיידי, ואחר כך כגיבוי ב-Activity.onCreate()של מרכז האפליקציות בהפעלה הראשונה של האפליקציה. - מחיקת מפתח השחזור ביציאה מהחשבון: כשהמשתמש יוצא מהחשבון או כשהשרת מבטל את תוקף הסשן, צריך להפעיל את
credentialManager.clearCredentialState()עםClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)ולנקות את דגל הסנכרון המקומי.
יצירת מפתח שחזור
האפליקציה צריכה לכסות את כל המקרים שבהם משתמש נכנס לחשבון, כדי לוודא שנוצר מפתח שחזור למשתמשים פעילים. צריך ליצור את מפתח השחזור בתרחישים הבאים:
- אם המשתמש מחובר לחשבון ועדיין לא נוצר מפתח שחזור (למשל ב-
onCreate()ל-Activityהראשי). - כשהמשתמש נכנס לחשבון או משלים תהליך הרשמה לחשבון חדש.
כדי להימנע מקריאה ל-createCredential() בכל הפעלה של האפליקציה, צריך לאחסן דגל סנכרון מקומי כמו has_synced_restore_credential בתוך האחסון המקומי. אל תאחסנו את הדגל הזה ב-SharedPreferences או ב-DataStore שמגובים כברירת מחדל, אלא אם הקובץ הזה לא נכלל ב-data_extraction_rules.xml. אחרת, הגיבוי האוטומטי יעתיק את has_synced_restore_credential = true למכשיר החדש וימנע מהמכשיר החדש ליצור או להחליף את מפתח השחזור שלו.
הפעלת Credential Manager
משתמשים בהקשר הפעילות של האפליקציה כדי ליצור מופע של אובייקט CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
קבלת אפשרויות ליצירת פרטי כניסה משרת האפליקציה
משתמשים בספרייה שתואמת ל-FIDO בשרת האפליקציה כדי לשלוח לאפליקציית הלקוח את קובץ ה-JSON של PublicKeyCredentialCreationOptions שנדרש ליצירת פרטי הכניסה לשחזור. מידע נוסף על ההטמעה בצד השרת זמין במאמר הנחיות לצד השרת.
ה-JSON של אפשרויות היצירה פועל לפי אותו מבנה כמו רישום מפתח הגישה:
{
"challenge": "<base64url-encoded challenge>",
"rp": {
"name": "<relying party name>",
"id": "<relying party host name>"
},
"user": {
"id": "<base64url-encoded user ID>",
"name": "<user name>",
"displayName": "<user display name>"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"attestation": "none",
"excludeCredentials": [
{
"id": "<base64url-encoded credential ID to exclude>",
"type": "public-key"
}
],
"authenticatorSelection": {
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
חשוב לוודא שקובץ ה-JSON של אפשרויות היצירה עומד בדרישות הבאות:
-
challengeו-user.id: מקודדים את שני הערכים כמחרוזות Base64URL ללא ריפוד (מחליפים את+ב--, את/ב-_ומסירים את התווים הנגררים=). תווים רגילים של Base64 או מזהים של מספרים שלמים גולמיים גורמים לשגיאותCreateRestoreCredentialDomExceptionאוIllegalArgumentException. -
rp.id: מגדירים את הדומיין של הצד המסתמך (לדוגמה,example.com). -
pubKeyCredParams: כולל את{"type": "public-key", "alg": -7}(ES256) ואת{"type": "public-key", "alg": -257}(RS256).
יצירת מפתח השחזור
אחרי שמקבלים מהשרת את קובץ ה-JSON של האפשרויות ליצירת מפתח ציבורי, עוטפים את קובץ ה-JSON באובייקט CreateRestoreCredentialRequest וקוראים ל-createCredential() באובייקט CredentialManager.
// createRestoreRequest contains the details sent by the server
val response = credentialManager.createCredential(context, createRestoreRequest)
מידע חשוב על הקוד
האובייקט
CreateRestoreCredentialRequestמכיל את השדות הבאים:-
requestJson: האפשרויות ליצירת פרטי כניסה שנשלחות על ידי שרת האפליקציה בפורמט של Web Authentication API עבורPublicKeyCredentialCreationOptionsJSON.
isCloudBackupEnabled: השדהBooleanכדי לקבוע אם צריך לגבות את מפתח השחזור בענן. כברירת מחדל, הדגל הזה הואtrue. הערכים בשדה הזה הם:-
true: (מומלץ) הערך הזה מאפשר לגבות את מפתחות השחזור בענן אם המשתמש הפעיל את הגיבוי של Google ואת ההצפנה מקצה לקצה, כמו נעילת מסך. -
false: הערך הזה שומר את המפתח באופן מקומי ולא בענן. המפתח לא זמין במכשיר החדש אם המשתמש בוחר לשחזר מהענן, אבל הוא עדיין מועבר במהלך הגדרה מקומית של מכשיר למכשיר (D2D) באמצעות כבל או Wi-Fi.
-
-
טיפול בתגובה ליצירת פרטי הכניסה
Credential Manager API מחזיר תשובה מסוג CreateRestoreCredentialResponse. התגובה הזו מכילה את התגובה לרישום פרטי הכניסה של המפתח הציבורי בפורמט JSON.
שולחים את response.registrationResponseJson מהאפליקציה לשרת של הצד המסתמך כדי לאמת את המפתח הציבורי ולשמור אותו לצד חשבון המשתמש, ואז מתעדים את סמן הסנכרון המקומי באחסון המקומי. מידע נוסף על אימות בצד השרת זמין בהנחיות למפתחות גישה.
במהלך תהליך היצירה של מפתח השחזור, צריך לטפל בחריגים הבאים:
-
CreateRestoreCredentialDomException: החריגה הזו מתרחשת אם הערך שלrequestJsonלא תקין ולא תואם לפורמט WebAuthn שלPublicKeyCredentialCreationOptionsJSON. -
E2eeUnavailableException: החריגה הזו מתרחשת אם הערך שלisCloudBackupEnabledהואtrue, אבל במכשיר של המשתמש אין גיבוי נתונים או הצפנה מקצה לקצה, כמו נעילת מסך.
כדי לוודא שפרטי הכניסה לשחזור נוצרים להעברות מקומיות ממכשיר למכשיר (D2D) גם כש-E2EE בענן לא זמין, צריך לתפוס אתE2eeUnavailableExceptionולנסות שוב מיד אתcreateCredential()עםCreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false). IllegalArgumentException: החריגה הזו מתרחשת אםrequestJsonריק, לא בפורמט JSON תקין או לא מכילuser.idתקין בקידוד Base64URL שתואם למפרטים של WebAuthn.
כניסה באמצעות מפתח שחזור
משתמשים בתכונה "שחזור פרטי הכניסה" כדי להכניס את המשתמש לחשבון באופן אוטומטי במהלך תהליך הגדרת המכשיר.
קבלת אפשרויות לאחזור פרטי כניסה משרת האפליקציה
לפני שקוראים ל-getCredential() במכשיר החדש, צריך לבקש אתגר JSON של PublicKeyCredentialRequestOptions ב-WebAuthn משרת האפליקציה. מכיוון שהאפליקציה במכשיר החדש עדיין לא יודעת איזה משתמש נכנס, נקודת הקצה הזו חייבת להיות לא מאומתת (גילוי ללא משתמש). למידע נוסף על השרת, קראו את המאמר כניסה באמצעות מפתח גישה ואת המדריך לאימות בצד השרת.
ל-JSON של אפשרויות השליפה יש את המבנה הבא:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
מגדירים את ה-JSON של אפשרויות השליפה בשרת באופן הבא:
-
allowCredentials: צריך להשמיט את השדה הזה או להעביר מערך ריק ([]) כי חשבון המשתמש לא ידוע לפני השחזור. אחרי ש-getCredential()מחזיר את התשובה, השרת מזהה את המשתמש לפי השדהresponse.userHandle(שתואם ל-user.idשנרשם במהלך היצירה) ומחפש את המפתח הציבורי לפיid(credentialId). -
userVerification: צריך להגדיר את הערך"discouraged"(או"preferred"). מכיוון שהתכונה "שחזור פרטי הכניסה" פועלת באופן שקט בלי ממשק משתמש ביומטרי או ממשק משתמש לנעילת המסך, מערכת Android משנה את הערך של"required"ל-"discouraged", ונתוני המאמת שמוחזרים לשרת כוללים את הדגל "המשתמש אומת" (uv) שמוגדר כ-false. מגדירים את מאמת שרת ה-WebAuthn כך שיקבל אתuv = falseעבור טענות נכוֹנוּת (assertions) של מפתח השחזור.
קבלת מפתח השחזור
כדי לקבל את מפתח השחזור במכשיר החדש, צריך לעטוף את ה-requestJson של השרת ב-GetRestoreCredentialOption בתוך GetCredentialRequest ולקרוא ל-getCredential() באובייקט CredentialManager.
מטמיעים אסטרטגיית שחזור דו-שלבית כדי שהמשתמשים ייכנסו לחשבון גם אם שחזור נתוני האפליקציה ברקע פועל וגם אם לא:
- רמה 1 (שחזור ברקע ב-
BackupAgent.onRestoreFinished()): אם הגיבוי של נתוני האפליקציה מופעל (android:allowBackup="true"), צריך ליצור מחלקת משנה שלBackupAgentולבטל אתonRestoreFinished()(לא להשתמש ב-onRestore(), שנקרא רק לגיבויים מדור קודם של ערכי מפתח). מכיוון ש-onRestoreFinished()היא פונקציה סינכרונית ו-getCredential()היא פונקצייתsuspendשל Kotlin, צריך לגשר על הקריאה באמצעותrunBlocking(Dispatchers.IO). תפיסה שקטה שלNoCredentialExceptionכשאין מפתח שחזור. - רמה 2 (חלופה בחזית במרכז האפליקציות
Activity.onCreate()): בהפעלה הראשונה של האפליקציה כשאין סשן מקומי, צריך להפעיל אתgetCredential()מ-lifecycleScopeלפני שמוצג מסך ההתחברות. הפקודה הזו מטפלת באפליקציות עםandroid:allowBackup="false"ומשחזרת את המצב אם המכשיר היה אופליין בזמן ש-onRestoreFinished()פעל.
אם מוסיפים BackupAgent חדש לאפליקציה שכבר משתמשת בגיבוי אוטומטי עם android:allowBackup="true", צריך גם להגדיר את android:fullBackupOnly="true" ברכיב <application> ב-AndroidManifest.xml כדי שמערכת Android תמשיך להשתמש בגיבוי אוטומטי במקום לעבור לגיבוי מדור קודם של ערכי מפתח.
// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()
// Create the GetRestoreCredentialRequest object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))
val response = credentialManager.getCredential(context, getRequest)
// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential
Credential Manager API מחזיר אובייקט GetCredentialResponse שהמאפיין credential שלו הוא RestoreCredential (ולא PublicKeyCredential) שמכיל את authenticationResponseJson.
טיפול בתגובה לכניסה לחשבון
שולחים את restoreCredential.authenticationResponseJson מהאפליקציה לשרת של הצד המסתמך כדי לאמת את טענת הנכוֹנוּת (assertion) הקריפטוגרפית ולהנפיק טוקן סשן חדש למשתמש. לפרטים על אימות בצד השרת, ראו כניסה באמצעות מפתח גישה.
כשאתם מתכננים את האחסון בקצה העורפי ואת כללי מדיניות מחזור החיים של מפתחות השחזור, חשוב לפעול לפי ההנחיות הבאות:
- הבחנה בין מפתחות שחזור לבין מפתחות גישה של משתמשים: תייגו את מפתחות השחזור במסד הנתונים שלכם כדי שהם יוסתרו ממסכי הניהול של מפתחות הגישה שגלויים למשתמשים, ויהיו פטורים מבדיקות אימות המשתמש (
uv = true). - תמיכה בכמה מכשירים פעילים לכל משתמש: למשתמש יכולים להיות כמה מכשירים (כמו טלפון וטאבלט). כדאי לאפשר כמה מפתחות שחזור פעילים לכל חשבון משתמש (לדוגמה, מפתחות לפי מזהה מכשיר או מזהה התקנה) במקום להחליף שורה אחת.
- הגדרת TTL מתאים וניקוי מפתחות מיותרים: הסרת אפליקציה או ניקוי נתוני האפליקציה מוחקים את מפתח השחזור המקומי בלי להודיע לשרת. שמרו את מפתחות השחזור למשך זמן ארוך מספיק כדי שישרדו את חלונות השדרוג של המכשיר (גם אם המשתמש יוצא מהחשבון במכשיר הישן אחרי ההעברה), וסלקו מפתחות לא פעילים על סמך זמן חיים (TTL) של חוסר פעילות או מגבלות מפתחות לכל משתמש.
מחיקת מפתח השחזור
Credential Manager הוא חסר מצב (stateless) ולא מודע לפעילות המשתמש, ולכן הוא לא מוחק באופן אוטומטי את מפתחות השחזור אחרי השימוש. כדי למחוק מפתח שחזור, צריך להפעיל את ה-method clearCredentialState(). מטעמי אבטחה, מחקו את המפתח בכל פעם שמשתמש יוצא מהחשבון, כדי שהוא לא ייכנס שוב לחשבון באופן אוטומטי בפעם הבאה שהוא יפעיל את האפליקציה.
מפתחות שחזור מוסרים רק במצבים הבאים:
- פעולות ברמת המערכת: המשתמשים מסירים את האפליקציה או מוחקים את הנתונים שלה.
- קריאות ברמת האפליקציה: מוחקים את המפתח באופן פרוגרמטי על ידי קריאה ל-
clearCredentialState()כשמטפלים ביציאה של המשתמש מהחשבון בקוד של האפליקציה.
מכיוון שברירת המחדל של ClearCredentialStateRequest() מנקה רק את מצב הסשן הרגיל ומשאירה את מפתח השחזור ללא שינוי, צריך לבצע ניקוי כפול ביציאה:
- צריך להפעיל את
credentialManager.clearCredentialState(ClearCredentialStateRequest())כדי לנקות את מצב הסשן הפעיל של ספק פרטי הכניסה. - קוראים ל-
credentialManager.clearCredentialState()עםClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)כדי למחוק את ה-RestoreCredentialששמור. - מוחקים את הסמן המקומי של
has_synced_restore_credentialמהאחסון המקומי.
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
// When the user logs out, delete the restore key
val response = credentialManager.clearCredentialState(clearRequest)
השלבים הבאים
- בדיקת התכונה "שחזור פרטי הכניסה": בודקים את תהליכי השחזור בענן וממכשיר למכשיר (D2D) באמצעות Android Studio או
adb shell bmgr. - בדיקות אוטומטיות של גיבוי ושחזור: כותבים בדיקות מארח של JUnit 5 באמצעות
BackupRestoreControllerכדי לאמת שחזורים שלCLOUD_ENCRYPTEDו-DEVICE_TO_DEVICEב-CI.