ממשקי ה-API של העברת פרטי כניסה ב-Credential Manager מאפשרים העברה מאובטחת של פרטי כניסה של משתמשים בין ספקי פרטי כניסה באותו מכשיר. במדריך הזה מוסבר איך ספקי פרטי כניסה ב-Android יכולים להשתלב עם ממשקי ה-API שסופקו על ידי הספרייה androidx.credentials:providerevents. התכונה הזו תומכת בסיסמאות, במפתחות גישה, בפרטי כתובת ובשדות מותאמים אישית באמצעות פורמט חילופי התקנים (CXF) של FIDO.
מושגי ליבה
מסגרת העברת פרטי הכניסה מאפשרת העברה של פרטי כניסה בין עמיתים במכשיר, בלי לחשוף פרטי כניסה גולמיים ל-Android OS או לאפליקציות לא מאומתות.
המסגרת מגדירה שני תפקידים עיקריים:
- הספק המייצא (ספק המקור): ספק פרטי כניסה שמחזיק כרגע בפרטי הכניסה של המשתמש. הוא מבצע רישום מראש של מטא-נתונים לגבי חשבונות זמינים שאפשר לייצא (
ExportEntry) למערכת, ומגיב לבקשות העברה כשהמשתמש בוחר אותו. - כלי ייבוא (ספק לקוח או אשף הגדרה): ספק אישורים או אשף הגדרה שיוזם בקשת ייבוא (
ImportCredentialsRequest) ומציין אילו סוגים של אישורים ותוספים הוא יכול לקבל.
תאימות לגרסת Android
ה-Credentials Transfer API פועל במכשירים עם Android מגרסה 8 (רמת API 26) ואילך.
הוספת יחסי תלות
מוסיפים את התלות androidx.credentials:providerevents אל build.gradle או build.gradle.kts של המודול:
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
יצירת מופע של המחלקה הנדרשת
יוצרים מופע של ProviderEventsManager הנדרש.
val providerEventsManager = ProviderEventsManager.create(context)
הטמעה של הכלי לייצוא
כדי לאפשר למשתמשים לייצא פרטי כניסה מהאפליקציה שלכם לספקי פרטי כניסה אחרים במכשיר, צריך להטמיע את תפקיד הייצואן.
רישום רשומות ייצוא
כשספק פרטי הכניסה משתנה (לדוגמה, משתמש נכנס, מוסיף פרטי כניסה או משנה חשבונות), צריך לרשום או לעדכן את הפריטים ExportEntry באמצעות ProviderEventsManager.registerExport().
כל ExportEntry דורש:
-
id: מזהה סודי של מחרוזת שנוצרת באופן אקראי ומייצגת באופן ייחודי את רשומת הייצוא הזו. חובה לשמור את המזהה הזה באופן מאובטח, כי תצטרכו אותו בהמשך כדי לאמת בקשות העברה נכנסות. -
accountDisplayName: תווית חשבון אופציונלית (לדוגמה,"Personal Account"). -
userDisplayName: המזהה הראשי של המשתמש (לדוגמה,"alice@example.com"). -
icon: סמלBitmapשמייצג את הספק או את החשבון (הספרייה משנה את הגודל שלו באופן אוטומטי ל-PNG בגודל 32x32). -
supportedCredentialTypes: קבוצה של קבועים מסוג מחרוזת מתוךCredentialTypesשמייצגים את הסוגים שהרשומה הזו מכילה.
suspend fun registerMyProviderForExport( providerEventsManager: ProviderEventsManager, providerIcon: Bitmap, // Randomly generated and stored in encrypted storage secretEntryId: String ) { val entry = ExportEntry( id = secretEntryId, accountDisplayName = "MyProvider Personal", userDisplayName = "alice@example.com", icon = providerIcon, supportedCredentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD ) ) // RegisterExportRequest.create() attaches the default WASM matcher from assets val request = RegisterExportRequest.create(context, listOf(entry)) try { val response = providerEventsManager.registerExport(request) // Registration successful } catch (e: Exception) { // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException) } }
הצהרה על פעילות הייצוא בקובץ המניפסט
כשהמשתמש בוחר את ExportEntry שלכם בממשק המשתמש של הכלי לבחירת מערכת, המערכת מפעילה את Activity הייעודית לטיפול. מצהירים על הפעילות הזו באמצעות פעולת ה-Intent וסכימת ה-URI של התוכן הנדרשות:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:label="@string/export_activity_label">
<intent-filter>
// This intent action is required for Credential Manager to invoke this activity
<action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
</intent-filter>
</activity>
טיפול בכוונה להעברה בפעילות
ב-CredentialExportActivity, משתמשים ב-IntentHandler.retrieveProviderImportCredentialsRequest(intent) כדי לנתח את הבקשה, לאמת את האפליקציה שקוראת ל-credId, לבצע אימות ביומטרי שנדרש ולכתוב את מטען ה-FIDO CXF בחזרה ל-URI של התוכן שסופק:
class CredentialExportActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. Extract the transfer request from the incoming Intent val request: ProviderImportCredentialsRequest? = IntentHandler.retrieveProviderImportCredentialsRequest(intent) if (request == null) { finishWithError() return } // 2. Validate CallingAppInfo and secret `credId` val callingAppPackage = request.callingAppInfo.packageName val receivedCredId = request.credId if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) { // Secret ID mismatch or untrusted caller -> abort sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request")) return } // 3. Optional: Prompt user for Biometric / PIN authentication before exporting authenticateUserThenExport(request) } private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) { // ... Biometric prompt logic ... // Once authenticated, generate the FIDO CXF JSON string matching the requested types val cxfJsonPayload = buildFidoCxfJsonPayload( requestedTypes = request.request.credentialTypes, requestedExtensions = request.request.knownExtensions ) val response = ImportCredentialsResponse(cxfJsonPayload) // 4. Write the JSON payload to the Content URI and set Activity result IntentHandler.setImportCredentialsResponse( context = this, uri = request.uri, intent = intent, response = response ) setResult(Activity.RESULT_OK, intent) finish() } private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) { IntentHandler.setImportCredentialsException(intent, exception) setResult(Activity.RESULT_OK, intent) finish() } private fun verifySecretEntryId(credentialId: String): Boolean { // Check if credentialId matches what you stored when calling RegisterExportRequest return credentialId == getStoredSecretEntryId() } private fun isTrustedImporter(packageName: String): Boolean { // Implement any specific allowlisting / caller checks if required return true } private fun finishWithError() { setResult(Activity.RESULT_CANCELED) finish() } }
ייצוא של רישום וניקוי חריגים
כל החריגים במודול הזה הם מחלקות משנה של ImportCredentialsException, RegisterExportException או ClearExportException.
-
RegisterExportProviderConfigurationExceptionאוClearExportProviderConfigurationException: השגיאה מופיעה כשההרשמה או הניקוי נכשלים בגלל בעיות בהגדרת הספק (לדוגמה,supportedCredentialTypesריק ב-ExportEntry, או קריאה בשכבת מערכת הפעלה לא נתמכת). -
RegisterExportUnknownErrorExceptionאו ClearExportUnknownErrorException: קרתה שגיאת מערכת או שגיאת אחסון לא מסווגת במהלך עדכון של רישום הייצוא.
הטמעה של כלי הייבוא
כדי לייבא פרטי כניסה לאפליקציה (לדוגמה, במהלך הדרכה למשתמשים חדשים או הייבוא של פלאגין שמתממשק עם שירותים חיצוניים), מטמיעים את תפקיד הייבוא ומתחילים את ה-Flow על ידי קריאה ל-ProviderEventsManager.importCredentials().
הרכבת בקשת הייבוא והפעלת התהליך
מציינים אילו CredentialTypes וKnownExtensions נתמכים בכלי הייבוא:
suspend fun startCredentialImport( activityContext: Context, providerEventsManager: ProviderEventsManager ) { val importRequest = ImportCredentialsRequest( credentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_NOTE ), knownExtensions = setOf( KnownExtensions.KNOWN_EXTENSION_SHARED ) ) try { // Launches the system Selector UI; suspends until user selects a provider and completes transfer val response = providerEventsManager.importCredentials(activityContext, importRequest) // 1. Inspect the source exporter's package info val exporterPackageName = response.callingAppInfo.packageName // 2. Parse the FIDO CXF JSON string val cxfJsonString = response.response.responseJson parseAndSaveImportedCredentials(cxfJsonString) } catch (e: ImportCredentialsException) { // Handle specific import exceptions (e.g., ImportCredentialsCancellationException) handleImportFailure(e) } } private fun parseAndSaveImportedCredentials(cxfJsonString: String) { val rootJson = JSONObject(cxfJsonString) // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification: // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html } // Helper function to make it compile private fun handleImportFailure(e: ImportCredentialsException) {}
ייבוא חריגים בתהליך
כל החריגים במודול הזה הם מחלקות משנה של ImportCredentialsException, RegisterExportException או ClearExportException.
-
ImportCredentialsCancellationException: המשתמש סגר את ממשק המשתמש של הכלי לבחירת חשבון או ביטל את פעולת הייצוא (Activity.RESULT_CANCELED). -
ImportCredentialsNoExportOptionException: לא נמצאו רשומות ייצוא רשומות שתואמות לסוגי פרטי הכניסה המבוקשים, או שהייצוא שנבחר הציג חריגה של דחייה. -
ImportCredentialsProviderConfigurationException: שגיאת הגדרה (לדוגמה,credentialTypesריק שמוגדר ב-ImportCredentialsRequestאו הרשאות חסרות). -
ImportCredentialsInvalidJsonException: הכלי לייצוא החזיר מטען ייעודי (payload) של JSON פגום או ריק, והאימות של הבקשה נכשל. -
ImportCredentialsSystemErrorException: אירעה שגיאה פנימית במערכת Android או שגיאת העברה של Binder במהלך הייבוא. -
ImportCredentialsUnknownCallerException: המסגרת לא הצליחה לאמת את האפליקציה שקוראת לפונקציה. -
ImportCredentialsUnknownErrorException: שגיאה לא צפויה או לא מסווגת במהלך תהליך היבוא.
סוגי פרטי הכניסה והתוספים הנתמכים
אובייקט androidx.credentials.providerevents.transfer.CredentialTypes מגדיר קבועים סטנדרטיים של מחרוזות שמתאימים לסוגי פריטים של FIDO CXF:
| קבוע | ערך (סוג cxf) |
תיאור |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
פרטי כניסה כמו שם משתמש וסיסמה. |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
פרטי כניסה של מפתח ציבורי של מפתח גישה מסוג FIDO2 או WebAuthn. |
CREDENTIAL_TYPE_ADDRESS |
"address" |
פרטי כתובת למשלוח או כתובת דואר למילוי אוטומטי של טפסים. |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
מפתחות גישה וטוקנים של API. |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
פרטי תשלום בכרטיס אשראי או בכרטיס חיוב מיידי. |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
קיבוצים מותאמים אישית או שדות שהמשתמש הגדיר. |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
פרטי רישיון הנהיגה. |
CREDENTIAL_TYPE_FILE |
"file" |
מטא-נתונים והפניות לפלייסהולדרים של קבצים בינאריים. |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
סיסמאות מאובטחות שנוצרו על ידי מחשב. |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
תעודות זהות, מספרי ביטוח לאומי, מספרי TIN או מספרי דרכון. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
קישורים לוגיים שמפנים לפריט אחר במטען הייעודי (payload). |
CREDENTIAL_TYPE_NOTE |
"note" |
הערות מאובטחות שהוגדרו על ידי המשתמש (מחרוזת UTF-8). |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
פרטי מסמך הנסיעה של הדרכון. |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
פרטי הזיהוי והשם של האדם. |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
זוגות של מפתחות SSH ציבוריים ופרטיים. |
CREDENTIAL_TYPE_TOTP |
"totp" |
סודות של סיסמאות חד-פעמיות מבוססות-זמן (2FA). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
מזהה ה-SSID של רשת Wi-Fi וסיסמאות הגישה. |
כלים מותאמים אישית להשוואה של WASM (WebAssembly) (מתקדם)
כברירת מחדל, כשמפעילים את הפונקציה RegisterExportRequest.create(context, entries), המערכת מאגדת את credential_transfer_matcher.wasm שמוגדר כברירת מחדל במערכת מתוך נכסי הספרייה, ומסננת את הרשומות על סמך החיתוך של supportedCredentialTypes.
אם ספק פרטי הכניסה שלכם דורש לוגיקת התאמה מורכבת (לדוגמה, בדיקות יכולות דינמיות או סינון מותנה על סמך שדות בהתאמה אישית), אתם יכולים לקמפל מודול WebAssembly משלכם שתואם ל-API של העברת פרטי הכניסה של WASM ולהעביר את מערך הבייטים הגולמי ישירות:
// Loading a custom WASM matcher val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() } val customRequest = RegisterExportRequest( entries = myEntries, exportMatcher = customMatcherBytes ) providerEventsManager.registerExport(customRequest)