Credentials Transfer API در Credential Manager امکان انتقال ایمن اطلاعات اعتباری کاربر را در همان دستگاه بین ارائهدهندگان اطلاعات اعتباری فراهم میکند. این راهنما جزئیات نحوه ادغام ارائهدهندگان اطلاعات کاربری در Android با میاناهای برنامهسازی کاربردی ارائهشده توسط کتابخانه androidx.credentials:providerevents را شرح میدهد. این ویژگی از گذرواژهها، گذرکلیدها، اطلاعات نشانی، و فیلدهای سفارشی بااستفاده از قالب استانداردشده تبادل اطلاعات کاربری FIDO (CXF) پشتیبانی میکند.
مفاهیم اصلی
چارچوب انتقال اطلاعات کاربری، انتقال همتابههمتای اطلاعات کاربری را در همان دستگاه تسهیل میکند، بدون اینکه اطلاعات کاربری خام را درمعرض سیستمعامل Android یا برنامههای تأییدنشده قرار دهد.
این چارچوب دو نقش اصلی را تعریف میکند:
- صادرکننده (ارائهدهنده منبع): ارائهدهنده اطلاعات اعتباری که درحالحاضر اطلاعات اعتباری کاربر را دراختیار دارد. این سرویس فراداده مربوط به حسابهای قابلصادر شدن دردسترس (
ExportEntry) را در سیستم پیشثبت میکند و وقتی کاربر آن را انتخاب میکند، به درخواستهای انتقال پاسخ میدهد. - واردکننده (ارائهدهنده کارخواه یا دستیار راهاندازی): ارائهدهنده اطلاعات اعتباری یا
دستیار راهاندازی که درخواست وارد کردن را آغاز میکند
(
ImportCredentialsRequest) و مشخص میکند چه نوع اطلاعات اعتباری و افزونههایی را میتواند دریافت کند.
سازگاری نسخه Android
Credentials Transfer API در دستگاههای دارای Android 8 (سطح میانای برنامهسازی کاربردی ۲۶) و بالاتر کار میکند.
افزودن وابستگیها
وابستگی 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 ۳۲×۳۲ مقیاسبندی میشود).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 رسیدگی تعیینشده شما را راهاندازی میکند. این فعالیت را با کنش هدف اجباری و طرحواره شناسه منبع یکسان محتوا اعلام کنید.
برای محافظت دربرابر فراخوانیهای غیرمجاز و حملات «معاون سردرگم»،
android:requireContentUriPermissionFromCaller="write" را در Android 14 (سطح ایپیآی
۳۴) و بالاتر اعلام کنید. این کار به سیستمعامل Android دستور میدهد تا قبلاز اجازه دادن به شروع فعالیت، تأیید کند که تماسگیرنده آغازکننده راهاندازی، اجازههای نوشتن در شناسه منبع یکسان محتوای مقصد را دارد، همانطور که در مثال زیر نشان داده شده است:
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:requireContentUriPermissionFromCaller="write"
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) برای تجزیه کردن
درخواست، درستیسنجی مؤلفه فراخواننده و فراداده درخواست، انجام هرگونه
اصالتسنجی زیستسنجشی لازم، و نوشتن بارداده FIDO CXF در
شناسه منبع یکسان محتوای ارائهشده استفاده کنید.
اصالتسنجی درخواست
چون فعالیتتان صادر میشود، با درستیسنجی هریک از لایههای زیر، از جریان صادر کردن اطلاعات کاربری محافظت کنید:
- درستیسنجی کردن
credIdنانس جلسه (دفاع اولیه): هنگام ثبت کردنExportEntry، یکid(نانس) با آنتروپی بالا و بهصورت تصادفی رمزنگاریشده تولید کنید و آن را با یک جلسه کوتاهمدت مرتبط کنید. در فعالیتتان، تأیید کنید کهrequest.credIdبا یک نانس جلسه فعال و منقضینشده که قبلاً توسط ارائهدهنده شما صادر شده است مطابقت دارد. چون برنامههای طرف سوم نمیتوانند این عدد یکبارمصرف را حدس بزنند، این کار تضمین میکند که قصد با انتخاب کاربر معتبر در انتخابگر سیستم مطابقت دارد. - درستیسنجی
Activity.getCallingPackage()(بررسی فراخواننده): چون انتخابگر اطلاعات کاربری فعالیت شما را بااستفاده ازstartActivityForResultراهاندازی میکند، فعالیت شما میتواندgetCallingPackage()را بررسی کند تا تأیید کند که فراخواننده فوری راهاندازیکننده میانای کاربر، چارچوب انتخابگر اطلاعات کاربری سیستم مورداعتماد است (در دستگاههای Android دارای خدمات Google Play،"com.google.android.gms"). اگر بسته فراخواننده ناشناخته است، درخواست را رد کنید. - بازرسی
request.callingAppInfo(اطلاعات برنامه هدف):callingAppInfoبرنامه مقصدی را که اعتبارنامههای واردشده را دریافت خواهد کرد شناسایی میکند. میتوانید از این اطلاعات برای شفافسازی (مثلاً نمایش نام و نماد برنامه مقصد در میانای تأیید) یا تأیید اینکه برنامه مقصد قابلاعتماد است یا نه استفاده کنید.
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: خطای سیستم یا فضای ذخیرهسازی طبقهبندینشده هنگام بهروزرسانی دفتر ثبت صادر کردن رخ داد.
پیادهسازی واردکننده
برای وارد کردن اطلاعات کاربری به برنامهتان (برای مثال، درطول آمادهسازی یا وارد کردن ارائهدهنده)، نقش واردکننده را پیادهسازی کنید و جریان را با فراخوانی 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: صادرکننده پایهبار JSON خالی یا بدساختی را برگرداند که اعتبارسنجی درخواست آن ناموفق بود.ImportCredentialsSystemErrorException: خطای انتقال Binder یا سیستم Android داخلی درطول وارد کردن روی داد.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" |
کلیدها و کدهای دسترسی میانای برنامهسازی کاربردی. |
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" |
کارت شناسایی ملی، شماره تأمین اجتماعی، شماره شناسایی مؤدی، یا ارجاعات گذرنامه. |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
پیوندهای منطقی که به مورد دیگری در بار داده اشاره میکنند. |
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" |
رمزهای گذرواژه یکبارمصرف زمانمحور (احراز هویت دومرحلهای). |
CREDENTIAL_TYPE_WIFI |
"wifi" |
شناسه SSID و گذرعبارتهای شبکه Wi-Fi. |
تطبیقدهندههای سفارشی WASM (WebAssembly) (پیشرفته)
بهطور پیشفرض، فراخوانی RegisterExportRequest.create(context, entries)، credential_transfer_matcher.wasm پیشفرض سیستم را از داراییهای کتابخانه بستهبندی میکند که ورودیها را صرفاً براساس تقاطع supportedCredentialTypes فیلتر میکند.
اگر ارائهدهنده اطلاعات کاربری شما به منطق تطبیق پیچیده نیاز دارد (برای مثال، بررسیهای قابلیت پویا یا فیلتر کردن شرطی براساس فیلدهای سفارشی)، میتوانید ماژول WebAssembly خودتان را که با WASM credential transfer API مطابقت دارد کامپایل کنید و آرایه بایت خام را مستقیماً ارسال کنید:
// 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)