پیاده‌سازی «بازگرداندن اعتبارنامه‌ها» با Credential Manager

این صفحه نحوه ایجاد، ورود به سیستم، و حذف کلید بازیابی را شرح می‌دهد.

سازگاری نسخه

«بازگرداندن اعتبارنامه‌ها» در «مدیر اعتبارنامه» در دستگاه‌های دارای Android 9 (سطح ای‌پی‌آی ۲۸) و بالاتر، نسخه اصلی 242200000 (v24.22) یا بالاتر «خدمات Google Play» (GMS)، و نسخه 1.5.0 یا بالاتر کتابخانه androidx.credentials کار می‌کند. در دستگاه‌های فاقد خدمات Google Play یا دارای نسخه قدیمی GMS، CreateCredentialProviderConfigurationException و GetCredentialProviderConfigurationException را بگیرید تا برنامه‌تان به‌راحتی به ورود به سیستم استاندارد برگردد.

پیش‌نیازها

سرور طرف متکی WebAuthn را مشابه سرور گذرکلیدها پیکربندی کنید. اگر ازقبل سروری برای رسیدگی به اصالت‌سنجی با گذرکلیدها راه‌اندازی کرده‌اید، می‌توانید از همان نقاط پایانی ثبت و ادعای WebAuthn برای کلیدهای بازیابی استفاده کنید.

وابستگی‌ها

وابستگی‌های زیر را به فایل build.gradle واحد برنامه‌تان اضافه کنید:

کاتلین

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha03")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
}

شیار

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha03"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
}

«بازگرداندن اعتبارنامه‌ها» از نسخه 1.5.0 و بالاتر کتابخانه androidx.credentials دردسترس است. بااین‌حال، توصیه می‌شود درصورت امکان از جدیدترین نسخه‌های پایدار وابستگی‌ها استفاده کنید.

نحوه تأثیر android:allowBackup بر «بازگرداندن اعتبارنامه‌ها»

مشخصه مانیفست android:allowBackup پشتیبان‌گیری داده‌های برنامه را کنترل می‌کند، که به سه روش متمایز با «بازگرداندن اعتبارنامه‌ها» تعامل دارد:

  • انتقال کلید بازیابی (مستقل از allowBackup): خدمات Google Play جفت کلید RestoreCredential را بااستفاده از پشتیبان‌گیری ابری رمزگذاری‌شده سرتاسر یا انتقال محلی دستگاه‌به‌دستگاه (D2D) منتقل می‌کند، صرف‌نظر از اینکه android:allowBackup، true یا false باشد. برای انتقال‌های دستگاه به دستگاه، کلید بازیابی همیشه منتقل می‌شود؛ تنها شرایطی که در آن منتقل نمی‌شود، درطول پشتیبان‌گیری ابری است، زمانی که کاربر در تنظیمات 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 کار می‌کند.

نمای کلی

«بازگرداندن اعتبارنامه‌ها» از چرخه حیات سه‌مرحله‌ای در دستگاه‌های قبلی و جدید کاربر پیروی می‌کند:

  1. ایجاد کلید بازیابی در دستگاه قبلی: پس‌از اینکه کاربر به سیستم وارد شد (یا هنگام راه‌اندازی برنامه اگر کاربر قبلاً به سیستم وارد شده باشد و هیچ کلید بازیابی‌ای در فضای ذخیره‌سازی محلی ثبت نشده باشد)، گزینه‌های ایجاد را از سرور طرف معتمدتان واکشی کنید و credentialManager.createCredential() را فراخوانی کنید.
  2. ورود به سیستم با کلید بازیابی در دستگاه جدید: RestoreCredential منتقل‌شده را بااستفاده از استراتژی دو سطحی بازیابی کنید: ابتدا به‌صورت بی‌صدا در BackupAgent.onRestoreFinished() (وقتی android:allowBackup="true") تا همگام‌سازی پس‌زمینه و اعلان‌های لحظه‌ای فوراً ازسر گرفته شود، و دوم به‌عنوان جایگزین در راه‌انداز Activity.onCreate() در اولین راه‌اندازی برنامه.
  3. حذف کلید بازیابی هنگام خروج از سیستم: وقتی کاربر از سیستم خارج می‌شود یا جلسه او توسط سرور نامعتبر می‌شود، 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) ایجاد می‌شود، حتی وقتی «رمزگذاری سرتاسر» ابری دردسترس نیست، 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 تنظیم کرده است. تأییدکننده سرور «اصالت‌سنجی وب» خود را پیکربندی کنید تا uv = false را برای ادعاهای کلید بازیابی بپذیرد.

دریافت کلید بازیابی

برای دریافت کلید بازیابی در دستگاه جدید، requestJson سرور را در GetRestoreCredentialOption داخل GetCredentialRequest بپیچید و getCredential() را در شیء CredentialManager فراخوانی کنید.

راهبرد بازیابی دو سطحی را پیاده‌سازی کنید تا کاربران چه بازیابی داده‌های برنامه پس‌زمینه اجرا شود یا نه، به سیستم وارد شوند:

  • سطح ۱ (بازیابی پس‌زمینه در BackupAgent.onRestoreFinished()): اگر پشتیبان‌گیری داده‌های برنامه فعال است (android:allowBackup="true")، زیرکلاس BackupAgent و لغو onRestoreFinished() (از onRestore() استفاده نکنید، که فقط برای پشتیبان‌گیری‌های قدیمی مقدار کلید فراخوانی می‌شود). چون onRestoreFinished() همگام است و getCredential() یک تابع suspend Kotlin است، تماس را بااستفاده از runBlocking(Dispatchers.IO) پل کنید. Catch NoCredentialException quietly when no restore key is present.
  • سطح ۲ (جایگزین پیش‌زمینه در راه‌انداز 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 را از برنامه به سرور طرف متکی خود ارسال کنید تا ادعای رمزنگاری را درستی‌سنجی کنید و کد جلسه جدیدی برای کاربر صادر کنید. برای جزئیات درستی‌سنجی سمت سرور، ورود به سیستم با گذرکلید را ببینید.

هنگام طراحی فضای ذخیره‌سازی پس‌زمینه و خط‌مشی‌های چرخه حیات برای کلیدهای بازیابی، این دستورالعمل‌ها را دنبال کنید:

  • کلیدهای بازیابی را از گذرکلیدهای کاربر متمایز کنید: کلیدهای بازیابی را در پایگاه داده‌تان برچسب‌گذاری کنید تا از صفحه‌های مدیریت گذرکلید سمت کاربر پنهان شوند و از بررسی‌های درستی‌سنجی کاربر (uv = true) معاف شوند.
  • پشتیبانی از چند دستگاه فعال برای هر کاربر: کاربر می‌تواند چند دستگاه (مثل تلفن و رایانه لوحی) داشته باشد. به‌جای بازنویسی یک ردیف، چند کلید بازیابی فعال برای هر حساب کاربری مجاز کنید (برای مثال، کلیدشده با شناسه دستگاه یا نصب).
  • تنظیم TTL مناسب و پاک کردن کلیدهای یتیم: حذف نصب برنامه یا پاک کردن داده‌های برنامه، کلید بازیابی محلی را بدون اطلاع دادن به سرور شما حذف می‌کند. کلیدهای بازیابی را به‌اندازه کافی نگه دارید تا در پنجره‌های ارتقای دستگاه باقی بمانند (حتی اگر کاربر پس‌از انتقال از سیستم دستگاه قدیمی خارج شود)، و کلیدهای قدیمی را براساس TTL عدم فعالیت یا محدودیت‌های کلید برای هر کاربر هرس کنید.

حذف کلید بازیابی

«مدیر اطلاعات کاربری» بدون حالت است و از فعالیت کاربر آگاه نیست، بنابراین کلیدهای بازیابی را پس‌از استفاده به‌طور خودکار حذف نمی‌کند. برای حذف کلید بازیابی، روش clearCredentialState() را فراخوانی کنید. برای امنیت، هرگاه کاربری از سیستم خارج شد، کلید را حذف کنید تا در راه‌اندازی بعدی برنامه، بدون اطلاع کاربر دوباره به سیستم وارد نشود.

کلیدهای بازیابی فقط در شرایط زیر برداشته می‌شوند:

  • کنش‌های سطح سیستم: کاربران برنامه را حذف نصب می‌کنند یا داده‌های آن را پاک می‌کنند.
  • فراخوانی‌های سطح برنامه: با فراخوانی clearCredentialState() هنگام رسیدگی به خروج کاربر از سیستم در کد برنامه‌تان، کلید را به‌صورت برنامه‌نویسی‌شده حذف کنید.

چون ClearCredentialStateRequest() پیش‌فرض فقط وضعیت جلسه استاندارد را پاک می‌کند و کلید بازیابی را دست‌نخورده باقی می‌گذارد، هنگام خروج از سیستم، پاک‌سازی دوگانه انجام دهید:

  1. برای پاک کردن وضعیت جلسه ارائه‌دهنده اطلاعات کاربری فعال، credentialManager.clearCredentialState(ClearCredentialStateRequest()) را فراخوانی کنید.
  2. برای حذف RestoreCredential ذخیره‌شده، با credentialManager.clearCredentialState() تماس بگیرید و ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) را ارائه دهید.
  3. نشانگر محلی 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)

مراحل بعدی