تنفيذ ميزة "استعادة بيانات الاعتماد" باستخدام "مدير بيانات الاعتماد"

توضّح هذه الصفحة كيفية إنشاء مفتاح استعادة وتسجيل الدخول باستخدامه وحذفه.

التوافق مع الإصدارات

تعمل ميزة "استعادة بيانات الاعتماد" في Credential Manager على الأجهزة التي تعمل بالإصدار 9 من Android (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث، والإصدار الأساسي 242200000 (الإصدار 24.22) من "خدمات Google Play"‏ (GMS) والإصدارات الأحدث، والإصدار 1.5.0 والإصدارات الأحدث من مكتبة androidx.credentials. على الأجهزة التي لا تتضمّن "خدمات Google Play" أو التي تتضمّن إصدارًا قديمًا من "خدمات Google للأجهزة الجوّالة"، يمكنك رصد CreateCredentialProviderConfigurationException وGetCredentialProviderConfigurationException ليعود تطبيقك بسلاسة إلى تسجيل الدخول العادي.

المتطلبات الأساسية

اضبط خادم الطرف المعتمِد في WebAuthn على غرار خادم مفاتيح المرور. إذا سبق لك إعداد خادم لمعالجة المصادقة باستخدام مفاتيح المرور، يمكنك إعادة استخدام نقاط نهاية التسجيل والتأكيد نفسها في 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 في الاحتفاظ بنسخة احتياطية من بيانات التطبيق، والتي تتفاعل مع واجهة Restore Credentials بثلاث طرق مختلفة:

  • استعادة نقل المفتاح (مستقل عن 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" (يتطلّب android:allowBackup="true"): يتطلّب اختبار نوع الاحتفاظ بنسخة احتياطية في السحابة الإلكترونية في أداة "الاحتفاظ بنسخة احتياطية والاستعادة" في "استوديو Android" 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) حتى في حال عدم توفّر ميزة "التشفير التام بين الأطراف" (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 لتأكيدات مفتاح الاستعادة.

الحصول على مفتاح الاستعادة

للحصول على مفتاح الاستعادة على الجهاز الجديد، عليك تضمين 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 من التطبيق إلى خادم الطرف المعتمِد للتحقّق من صحة بيان التشفير وإصدار الرمز المميز للجلسة للمستخدم. للاطّلاع على تفاصيل عملية التحقّق من جهة الخادم، يُرجى مراجعة مقالة تسجيل الدخول باستخدام مفتاح مرور.

عند تصميم مساحة التخزين في الخلفية وسياسات مراحل النشاط لمفاتيح الاستعادة، يُرجى اتّباع الإرشادات التالية:

  • التمييز بين مفاتيح الاستعادة ومفاتيح مرور المستخدمين: يمكنك وضع علامة على مفاتيح الاستعادة في قاعدة البيانات لإخفائها من شاشات إدارة مفاتيح المرور الموجّهة للمستخدمين واستثنائها من عمليات التحقّق من المستخدم (uv = true).
  • إتاحة استخدام أجهزة نشطة متعددة لكل مستخدم: يمكن أن يمتلك المستخدم أجهزة متعددة (مثل هاتف وجهاز لوحي). يجب السماح باستخدام عدة مفاتيح استعادة نشطة لكل حساب مستخدم (على سبيل المثال، مفاتيح حسب الجهاز أو رقم تعريف التثبيت) بدلاً من استبدال صف واحد.
  • ضبط مدة صلاحية مناسبة وتنظيف المفاتيح المعزولة: يؤدي إلغاء تثبيت تطبيق أو محو بياناته إلى حذف مفتاح الاستعادة على الجهاز بدون إشعار الخادم. يجب الاحتفاظ بمفاتيح الاستعادة لفترة كافية تتيح ترقية الأجهزة (حتى إذا سجّل المستخدم الخروج من الجهاز القديم بعد نقل البيانات)، ويجب حذف المفاتيح القديمة استنادًا إلى مدة صلاحية عدم النشاط أو حدود المفاتيح لكل مستخدم.

حذف مفتاح الاستعادة

لا يحتفظ Credential Manager بالبيانات ولا يطّلع على نشاط المستخدم، لذا لا يحذف مفاتيح الاستعادة تلقائيًا بعد الاستخدام. لحذف مفتاح استعادة، يمكنك استدعاء الطريقة clearCredentialState(). لأسباب تتعلق بالأمان، احذف المفتاح عندما يسجّل المستخدم الخروج حتى لا يتم تسجيل دخوله تلقائيًا عند تشغيل التطبيق في المرة التالية.

لا تتم إزالة مفاتيح الاستعادة إلا في الحالات التالية:

  • الإجراءات على مستوى النظام: يلغي المستخدمون تثبيت التطبيق أو يمحون بياناته.
  • الطلبات على مستوى التطبيق: يمكنك حذف المفتاح آليًا من خلال طلب clearCredentialState() عند معالجة تسجيل خروج المستخدم في رمز تطبيقك.

بما أنّ الإجراء التلقائي ClearCredentialStateRequest() يمحو فقط حالة الجلسة العادية ويترك مفتاح الاستعادة سليمًا، يمكنك إجراء محو مزدوج عند تسجيل الخروج:

  1. يمكنك استدعاء credentialManager.clearCredentialState(ClearCredentialStateRequest()) لمحو حالة جلسة موفّر بيانات الاعتماد النشطة.
  2. يمكنك الاتصال بالرقم credentialManager.clearCredentialState() باستخدام ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) لحذف RestoreCredential المخزَّنة.
  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)

الخطوات التالية