دمج "مدير بيانات الاعتماد" مع حلّ موفّر بيانات الاعتماد

يشير "مدير بيانات الاعتماد" إلى مجموعة من واجهات برمجة التطبيقات التي تم توفيرها في Android 14 والتي تتيح استخدام طرق تسجيل دخول متعدّدة، مثل اسم المستخدم-كلمة المرور ومفاتيح المرور وحلول تسجيل الدخول الموحّد (مثل ميزة "تسجيل الدخول باستخدام حساب Google"). عند استدعاء واجهة برمجة تطبيقات مدير بيانات الاعتماد، يجمع نظام Android بيانات الاعتماد من جميع مقدّمي بيانات الاعتماد الذين تم تثبيتهم على الجهاز. يصف هذا المستند مجموعة من واجهات برمجة التطبيقات التي توفر نقاط نهاية الدمج لمزودي بيانات الاعتماد هؤلاء.

ضبط إعدادات الجهاز

قبل تنفيذ الوظيفة في موفِّر بيانات الاعتماد، أكمِل خطوات الإعداد الموضّحة في الأقسام التالية.

تعريف التبعيات

في ملف build.gradle الخاص بالوحدة، حدِّد تبعية باستخدام أحدث إصدار من مكتبة "مدير بيانات الاعتماد":

implementation "androidx.credentials:credentials:1.2.0-{latest}"

تعريف عنصر الخدمة في ملف البيان

في ملف البيان الخاص بتطبيقك AndroidManifest.xml، عليك تضمين تعريف <service> لفئة خدمة تعمل على توسيع نطاق فئة CredentialProviderService من مكتبة androidx.credentials، كما هو موضّح في المثال أدناه.

<service android:name=".MyCredentialProviderService"
         android:enabled="true"
         android:exported="true"
         android:label="My Credential Provider"
         android:icon="<any drawable icon>"
         android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
    <intent-filter>
        <action android:name="android.service.credentials.CredentialProviderService"/>
    </intent-filter>
    <meta-data
         android:name="android.credentials.provider"
         android:resource="@xml/provider"/>
</service>

يُعد الإذن وفلتر الأهداف الموضحان أعلاه جزءًا لا يتجزأ من سير عمل "مدير بيانات الاعتماد" على النحو المتوقع. الإذن مطلوب حتى لا يتمكن سوى نظام Android من الربط بهذه الخدمة. يُستخدم فلتر الأهداف لاكتشاف هذه الخدمة كمزوّد بيانات اعتماد ليتم استخدامها بواسطة "مدير بيانات الاعتماد".

توضيح أنواع بيانات الاعتماد المتوافقة

في دليل res/xml، أنشِئ ملفًا جديدًا باسم provider.xml. في هذا الملف، عرِّف أنواع بيانات الاعتماد التي توفّرها الخدمة من خلال الثوابت المحددة لكل نوع من أنواع بيانات الاعتماد في المكتبة. في المثال التالي، تتيح الخدمة استخدام كلمات المرور التقليدية، بالإضافة إلى مفاتيح المرور، التي يتم تعريفها على أنّها TYPE_PASSWORD_CREDENTIAL وTYPE_PUBLIC_KEY_CREDENTIAL:

<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
   <capabilities>
       <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
       <capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
   </capabilities>
</credential-provider>

في مستويات واجهات برمجة التطبيقات السابقة، يتكامل موفرو بيانات الاعتماد مع واجهات برمجة التطبيقات مثل ميزة الملء التلقائي لكلمات المرور والبيانات الأخرى. ويمكن لمقدمي البيانات هؤلاء استخدام البنية الأساسية الداخلية نفسها لتخزين أنواع بيانات الاعتماد الحالية، مع توسيع نطاقها ليشمل بيانات الاعتماد الأخرى، بما في ذلك مفاتيح المرور.

أسلوب على مرحلتين للتفاعل مع مقدّم الخدمة

يتفاعل "مدير بيانات الاعتماد" مع مزوِّدي بيانات الاعتماد في مرحلتين:

  1. المرحلة الأولى هي مرحلة البدء/الاستعلام حيث يرتبط النظام بخدمات مقدم بيانات الاعتماد ويستدعي طرق onBeginGetCredentialRequest() أو onBeginCreateCredentialRequest() أو onClearCredentialStateRequest() مع طلبات Begin…. على مقدّمي الخدمات معالجة هذه الطلبات والاستجابة بردود Begin…، وملئها بالإدخالات التي تمثّل الخيارات المرئية التي سيتم عرضها في أداة اختيار الحساب. يجب أن يحتوي كل إدخال على مجموعة PendingIntent.
  2. بعد أن يختار المستخدم إدخالاً، تبدأ مرحلة الاختيار ويتم تنشيط PendingIntent المرتبطة بالإدخال، ما يؤدي إلى عرض نشاط مقدّم الخدمة المقابل. وبمجرد أن ينتهي المستخدم من التفاعل مع هذا النشاط، يجب على موفر بيانات الاعتماد تعيين الاستجابة لنتيجة النشاط قبل إنهائه. بعد ذلك، يتم إرسال هذه الاستجابة إلى تطبيق العميل الذي استدعى "مدير بيانات الاعتماد".

معالجة عملية إنشاء مفتاح المرور

معالجة طلبات البحث لإنشاء مفاتيح مرور

عندما يريد أحد تطبيقات العميل إنشاء مفتاح مرور وتخزينه باستخدام مزوّد بيانات اعتماد، فإنّه يستدعي واجهة برمجة تطبيقات createCredential. لمعالجة هذا الطلب في خدمة مقدِّم بيانات الاعتماد بحيث يتم تخزين مفتاح المرور فعليًا في مساحة التخزين، أكمِل الخطوات الموضّحة في الأقسام التالية.

  1. يمكنك إلغاء طريقة onBeginCreateCredentialRequest() في الخدمة الممتدة من CredentialProviderService.
  2. يمكنك التعامل مع BeginCreateCredentialRequest من خلال إنشاء BeginCreateCredentialResponse مكافئة وتمريرها من خلال رد الاتصال.
  3. أثناء إنشاء BeginCreateCredentialResponse، أضِف CreateEntries المطلوب. يجب أن يتوافق كل CreateEntry مع حساب يمكن فيه حفظ بيانات الاعتماد، كما يجب ضبط PendingIntent مع البيانات الوصفية الأخرى المطلوبة.

يوضّح المثال التالي كيفية تنفيذ هذه الخطوات.

override fun onBeginCreateCredentialRequest(
  request: BeginCreateCredentialRequest,
  cancellationSignal: CancellationSignal,
  callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
  val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
  if (response != null) {
    callback.onResult(response)
  } else {
    callback.onError(CreateCredentialUnknownException())
  }
}

fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
  when (request) {
    is BeginCreatePublicKeyCredentialRequest -> {
      // Request is passkey type
      return handleCreatePasskeyQuery(request)
    }
  }
  // Request not supported
  return null
}

private fun handleCreatePasskeyQuery(
    request: BeginCreatePublicKeyCredentialRequest
    ): BeginCreateCredentialResponse {

    // Adding two create entries - one for storing credentials to the 'Personal'
    // account, and one for storing them to the 'Family' account. These
    // accounts are local to this sample app only.
    val createEntries: MutableList<CreateEntry> = mutableListOf()
    createEntries.add( CreateEntry(
        PERSONAL_ACCOUNT_ID,
        createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
    ))

    createEntries.add( CreateEntry(
        FAMILY_ACCOUNT_ID,
        createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
    ))

    return BeginCreateCredentialResponse(createEntries)
}

private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
    val intent = Intent(action).setPackage(PACKAGE_NAME)

    // Add your local account ID as an extra to the intent, so that when
    // user selects this entry, the credential can be saved to this
    // account
    intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)

    return PendingIntent.getActivity(
        applicationContext, UNIQUE_REQ_CODE,
        intent, (
            PendingIntent.FLAG_MUTABLE
            or PendingIntent.FLAG_UPDATE_CURRENT
        )
    )
}

يجب أن تتقيّد عملية إنشاء PendingIntent بما يلي:

  • يجب إعداد النشاط المقابل لعرض أي طلب مطلوب بشأن المقاييس الحيوية أو التأكيد أو التحديد المطلوب.
  • أي بيانات مطلوبة يحتاجها مقدّم الخدمة عند استدعاء النشاط المقابل، يجب ضبطها كبيانات إضافية في الغرض المستخدَم لإنشاء PendingIntent، مثل accountId في عملية الإنشاء.
  • يجب إنشاء PendingIntent باستخدام العلامة PendingIntent.FLAG_MUTABLE حتى يتمكّن النظام من إلحاق الطلب الأخير بالهدف الإضافي.
  • يجب ألا يتم إنشاء PendingIntent باستخدام العلامة PendingIntent.FLAG_ONE_SHOT لأنّ المستخدم قد يختار إدخالاً، لذا عليك الرجوع وإعادة اختياره، ما سيؤدي إلى تنشيط PendingIntent مرتين.
  • يجب إنشاء PendingIntent باستخدام رمز طلب فريد لكي يكون لكل إدخال PendingIntent مطابق خاص به.

اختيار الاسم المعرِّف لطلبات إنشاء مفاتيح المرور

  1. عندما يختار المستخدم CreateEntry التي تمت تعبئتها سابقًا، يتم استدعاء PendingIntent المقابل ويتم إنشاء Activity.
  2. بعد استدعاء طريقة onCreate في "نشاطك"، يمكنك الوصول إلى الغرض ذي الصلة وتمريره إلى صف PendingIntentHander للحصول على ProviderCreateCredentialRequest.
  3. استخرِج requestJson وcallingAppInfo وclientDataHash من الطلب.
  4. استخرِج accountId المحلي من النية الإضافية. هذا نموذج تنفيذ خاص بالتطبيق وليس مطلوبًا. يمكن استخدام رقم تعريف الحساب هذا لتخزين بيانات الاعتماد هذه مقابل رقم تعريف الحساب المحدد هذا.
  5. تحقَّق من صحة requestJson. يستخدم المثال أدناه فئات البيانات المحلية مثل PublicKeyCredentialCreationOptions لتحويل JSON الإدخال إلى فئة منظَّمة وفقًا لمواصفات WebAuthn. بصفتك موفِّر بيانات اعتماد، يمكنك استبداله بمحللك اللغوي.
  6. تحقَّق من رابط مادة العرض لتطبيق الاتصال إذا كانت المكالمة واردة من تطبيق Android أصلي.
  7. عرض إشعار للمصادقة يستخدم المثال أدناه واجهة برمجة التطبيقات Biometric API لنظام التشغيل Android.
  8. عند نجاح المصادقة، أنشِئ credentialId ومفتاحَي مفاتيح.
  9. احفظ المفتاح الخاص في قاعدة البيانات المحلية مقابل callingAppInfo.packageName.
  10. أنشِئ استجابة JSON لواجهة برمجة تطبيقات مصادقة الويب التي تتألّف من المفتاح العام والرمز credentialId. يستخدم المثال أدناه فئات الخدمات المحلية مثل AuthenticatorAttestationResponse وFidoPublicKeyCredential التي تساعد في إنشاء JSON استنادًا إلى المواصفات المذكورة سابقًا.وبصفتك موفِّر بيانات اعتماد، يمكنك استبدال هذه الفئات بأدوات الإنشاء الخاصة بك.
  11. أنشِئ CreatePublicKeyCredentialResponse باستخدام JSON أعلاه.
  12. اضبط CreatePublicKeyCredentialResponse كعنصر إضافي على Intent حتى PendingIntentHander.setCreateCredentialResponse()، واضبط هذا القصد على نتيجة النشاط.
  13. أنهِ النشاط.

يوضح مثال الرمز أدناه هذه الخطوات. يجب معالجة هذا الرمز في فئة النشاط بمجرد استدعاء onCreate().

val request =
  PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)

val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
  val publicKeyRequest: CreatePublicKeyCredentialRequest =
    request.callingRequest as CreatePublicKeyCredentialRequest
  createPasskey(
    publicKeyRequest.requestJson,
    request.callingAppInfo,
    publicKeyRequest.clientDataHash,
    accountId
  )
}

fun createPasskey(
  requestJson: String,
  callingAppInfo: CallingAppInfo?,
  clientDataHash: ByteArray?,
  accountId: String?
) {
  val request = PublicKeyCredentialCreationOptions(requestJson)

  val biometricPrompt = BiometricPrompt(
    this,
    <executor>,
    object : BiometricPrompt.AuthenticationCallback() {
      override fun onAuthenticationError(
        errorCode: Int, errString: CharSequence
      ) {
        super.onAuthenticationError(errorCode, errString)
        finish()
      }

      override fun onAuthenticationFailed() {
        super.onAuthenticationFailed()
        finish()
      }

      override fun onAuthenticationSucceeded(
        result: BiometricPrompt.AuthenticationResult
      ) {
        super.onAuthenticationSucceeded(result)

        // Generate a credentialId
        val credentialId = ByteArray(32)
        SecureRandom().nextBytes(credentialId)

        // Generate a credential key pair
        val spec = ECGenParameterSpec("secp256r1")
        val keyPairGen = KeyPairGenerator.getInstance("EC");
        keyPairGen.initialize(spec)
        val keyPair = keyPairGen.genKeyPair()

        // Save passkey in your database as per your own implementation

        // Create AuthenticatorAttestationResponse object to pass to
        // FidoPublicKeyCredential

        val response = AuthenticatorAttestationResponse(
          requestOptions = request,
          credentialId = credentialId,
          credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
          origin = appInfoToOrigin(callingAppInfo),
          up = true,
          uv = true,
          be = true,
          bs = true,
          packageName = callingAppInfo.packageName
        )

        val credential = FidoPublicKeyCredential(
          rawId = credentialId, response = response
        )
        val result = Intent()

        val createPublicKeyCredResponse =
          CreatePublicKeyCredentialResponse(credential.json())

        // Set the CreateCredentialResponse as the result of the Activity
        PendingIntentHandler.setCreateCredentialResponse(
          result, createPublicKeyCredResponse
        )
        setResult(Activity.RESULT_OK, result)
        finish()
      }
    }
  )

  val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Use your screen lock")
    .setSubtitle("Create passkey for ${request.rp.name}")
    .setAllowedAuthenticators(
        BiometricManager.Authenticators.BIOMETRIC_STRONG
        /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
      )
    .build()
  biometricPrompt.authenticate(promptInfo)
}

fun appInfoToOrigin(info: CallingAppInfo): String {
  val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
  val md = MessageDigest.getInstance("SHA-256");
  val certHash = md.digest(cert)
  // This is the format for origin
  return "android:apk-key-hash:${b64Encode(certHash)}"
}

التعامل مع الطلبات الخاصة بطلبات إنشاء كلمات المرور

للتعامل مع طلبات البحث في طلبات إنشاء كلمة المرور، قم بما يلي:

  • داخل طريقة processCreateCredentialRequest() المذكورة في القسم السابق، أضِف حالة أخرى داخل كتلة المفاتيح للتعامل مع طلبات كلمات المرور.
  • أثناء إنشاء السمة BeginCreateCredentialResponse، أضِف السمة CreateEntries المطلوبة.
  • يجب أن يتوافق كل CreateEntry مع حساب يمكن فيه حفظ بيانات الاعتماد، ويجب ضبط PendingIntent عليه بالإضافة إلى بيانات وصفية أخرى.

يوضّح المثال التالي كيفية تنفيذ هذه الخطوات:

fun processCreateCredentialRequest(
    request: BeginCreateCredentialRequest
  ): BeginCreateCredentialResponse? {
  when (request) {
    is BeginCreatePublicKeyCredentialRequest -> {
      // Request is passkey type
      return handleCreatePasskeyQuery(request)
    }

    is BeginCreatePasswordCredentialRequest -> {
    // Request is password type
      return handleCreatePasswordQuery(request)
    }
  }
  return null
}

private fun handleCreatePasswordQuery(
    request: BeginCreatePasswordCredentialRequest
  ): BeginCreateCredentialResponse {
  val createEntries: MutableList<CreateEntry> = mutableListOf()

  // Adding two create entries - one for storing credentials to the 'Personal'
  // account, and one for storing them to the 'Family' account. These
  // accounts are local to this sample app only.
  createEntries.add(
    CreateEntry(
      PERSONAL_ACCOUNT_ID,
      createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
    )
  )
  createEntries.add(
    CreateEntry(
      FAMILY_ACCOUNT_ID,
      createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
    )
  )

  return BeginCreateCredentialResponse(createEntries)
}

اختيار الأسماء المعرِّفة لطلبات إنشاء كلمة المرور

عندما يختار المستخدم CreateEntry تمت تعبئته، يتم تنفيذ النشاط المرتبط في السمة PendingIntent وعرض النشاط المرتبط به. يمكنك الوصول إلى النية المرتبطة التي تم تمريرها في onCreate وإدراجها في الفئة PendingIntentHander للحصول على طريقة ProviderCreateCredentialRequest.

يوضّح المثال التالي كيفية تنفيذ هذه العملية. يجب التعامل مع هذا الرمز في طريقة onCreate() في "نشاطك".

val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)

val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest

// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
    PasswordInfo(
        request.id,
        request.password,
        createRequest.callingAppInfo.packageName
    )
)

//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()

التعامل مع عملية تسجيل دخول المستخدم

يتم التعامل مع تسجيل دخول المستخدم من خلال الخطوات التالية:

  • عندما يحاول تطبيق عميل تسجيل دخول مستخدم، يعمل على إعداد مثيل GetCredentialRequest.
  • ينشر إطار عمل Android هذا الطلب لجميع موفِّري بيانات الاعتماد السارية من خلال الربط بهذه الخدمات.
  • بعد ذلك، تتلقّى خدمة مقدّم الخدمة عنصر BeginGetCredentialRequest يحتوي على قائمة تتضمّن BeginGetCredentialOption، يحتوي كل منها على مَعلَمات يمكن استخدامها لاسترداد بيانات الاعتماد المطابِقة.

لمعالجة هذا الطلب في خدمة موفِّر بيانات الاعتماد، أكمِل الخطوات التالية:

  1. يمكنك إلغاء طريقة onBeginGetCredentialRequest() لمعالجة الطلب. يُرجى العِلم أنّه إذا كانت بيانات الاعتماد مقفلة، يمكنك ضبط AuthenticationAction على الفور في الاستجابة واستدعاء معاودة الاتصال.

    private val unlockEntryTitle = "Authenticate to continue"
    
    override fun onBeginGetCredentialRequest(
        request: BeginGetCredentialRequest,
        cancellationSignal: CancellationSignal,
        callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
    ) {
        if (isAppLocked()) {
            callback.onResult(BeginGetCredentialResponse(
                authenticationActions = mutableListOf(AuthenticationAction(
                    unlockEntryTitle, createUnlockPendingIntent())
                    )
                )
            )
            return
        }
        try {
            response = processGetCredentialRequest(request)
            callback.onResult(response)
        } catch (e: GetCredentialException) {
            callback.onError(GetCredentialUnknownException())
        }
    }
    

    بالنسبة إلى مقدّمي الخدمات الذين يطلبون فتح قفل بيانات الاعتماد قبل عرض أي credentialEntries، عليهم إعداد هدف في انتظار المراجعة ينقل المستخدم إلى مسار فتح قفل التطبيق:

    private fun createUnlockPendingIntent(): PendingIntent {
        val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME)
        return PendingIntent.getActivity(
        applicationContext, UNIQUE_REQUEST_CODE, intent, (
            PendingIntent.FLAG_MUTABLE
            or PendingIntent.FLAG_UPDATE_CURRENT
            )
        )
    }
    
  2. يمكنك استرداد بيانات الاعتماد من قاعدة البيانات المحلية وإعدادها باستخدام CredentialEntries لعرضها على أداة الاختيار. بالنسبة إلى مفاتيح المرور، يمكنك ضبط credentialId كعنصر إضافي في الغرض من أجل معرفة بيانات الاعتماد التي يتم ربطها عندما يختار المستخدم هذا الإدخال.

    companion object {
        // These intent actions are specified for corresponding activities
        // that are to be invoked through the PendingIntent(s)
        private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY"
        private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD"
    
    }
    
    fun processGetCredentialsRequest(
    request: BeginGetCredentialRequest
    ): BeginGetCredentialResponse {
        val callingPackage = request.callingAppInfo?.packageName
        val credentialEntries: MutableList<CredentialEntry> = mutableListOf()
    
        for (option in request.beginGetCredentialOptions) {
            when (option) {
                is BeginGetPasswordOption -> {
                    credentialEntries.addAll(
                            populatePasswordData(
                                callingPackage,
                                option
                            )
                        )
                    }
                    is BeginGetPublicKeyCredentialOption -> {
                        credentialEntries.addAll(
                            populatePasskeyData(
                                callingPackage,
                                option
                            )
                        )
                    )
                } else -> {
                    Log.i(TAG, "Request not supported")
                }
            }
        }
        return BeginGetCredentialResponse(credentialEntries)
    }
    
  3. الاستعلام عن بيانات الاعتماد من قاعدة البيانات وإنشاء مفتاح المرور وإدخالات كلمة المرور للملء.

    private fun populatePasskeyData(
        callingAppInfo: CallingAppInfo,
        option: BeginGetPublicKeyCredentialOption
    ): List<CredentialEntry> {
      val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
      val request = PublicKeyCredentialRequestOptions(option.requestJson)
      // Get your credentials from database where you saved during creation flow
      val creds = <getCredentialsFromInternalDb(request.rpId)>
      val passkeys = creds.passkeys
      for (passkey in passkeys) {
          val data = Bundle()
          data.putString("credId", passkey.credId)
          passkeyEntries.add(
              PublicKeyCredentialEntry(
                  context = applicationContext,
                  username = passkey.username,
                  pendingIntent = createNewPendingIntent(
                      GET_PASSKEY_INTENT_ACTION,
                      data
                  ),
                  beginPublicKeyCredentialOption = option,
                  displayName = passkey.displayName,
                  icon = passkey.icon
              )
          )
      }
      return passkeyEntries
    }
    
    // Fetch password credentials and create password entries to populate to
    // the user
    private fun populatePasswordData(
    callingPackage: String,
    option: BeginGetPasswordOption
    ): List<CredentialEntry> {
        val passwordEntries: MutableList<CredentialEntry> = mutableListOf()
    
        // Get your password credentials from database where you saved during
        // creation flow
        val creds = <getCredentialsFromInternalDb(callingPackage)>
        val passwords = creds.passwords
        for (password in passwords) {
            passwordEntries.add(
                PasswordCredentialEntry(
                    context = applicationContext,
                    username = password.username,
                    pendingIntent = createNewPendingIntent(
                    GET_PASSWORD_INTENT
                    ),
                    beginGetPasswordOption = option
                        displayName = password.username,
                    icon = password.icon
                )
            )
        }
        return passwordEntries
    }
    
    private fun createNewPendingIntent(
        action: String,
        extra: Bundle? = null
    ): PendingIntent {
        val intent = Intent(action).setPackage(PACKAGE_NAME)
        if (extra != null) {
            intent.putExtra("CREDENTIAL_DATA", extra)
        }
    
        return PendingIntent.getActivity(
            applicationContext, UNIQUE_REQUEST_CODE, intent,
            (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
        )
    }
    
  4. بعد طلب بيانات الاعتماد وملؤها، عليك الآن معالجة مرحلة اختيار بيانات الاعتماد التي يختارها المستخدم، سواء كانت مفتاح مرور أو كلمة مرور.

معالجة اختيار المستخدمين لمفاتيح المرور

  1. في طريقة onCreate للنشاط المقابل، استرِدّ الغرض المرتبط وانقله إلى PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. استخرِج GetPublicKeyCredentialOption من الطلب الذي تم استرداده أعلاه. بعد ذلك، استخرِج requestJson وclientDataHash من هذا الخيار.
  3. استخرِج credentialId من عنصر intent الإضافي، الذي تمت تعبئته من خلال موفِّر بيانات الاعتماد عند إعداد PendingIntent المقابل.
  4. استخرِج مفتاح المرور من قاعدة البيانات المحلية باستخدام معلمات الطلب التي تم الوصول إليها أعلاه.
  5. تأكَّد من أنّ مفتاح المرور صالح مع البيانات الوصفية المستخلصة وإثبات ملكية المستخدم.

    val getRequest =
        PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    val publicKeyRequest =
    getRequest.credentialOption as GetPublicKeyCredentialOption
    
    val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
    val credIdEnc = requestInfo.getString("credId")
    
    // Get the saved passkey from your database based on the credential ID
    // from the publickeyRequest
    val passkey = <your database>.getPasskey(credIdEnc)
    
    // Decode the credential ID, private key and user ID
    val credId = b64Decode(credIdEnc)
    val privateKey = b64Decode(passkey.credPrivateKey)
    val uid = b64Decode(passkey.uid)
    
    val origin = appInfoToOrigin(getRequest.callingAppInfo)
    val packageName = getRequest.callingAppInfo.packageName
    
    validatePasskey(
        publicKeyRequest.requestJson,
        origin,
        packageName,
        uid,
        passkey.username,
        credId,
        privateKey
    )
    
  6. للتحقق من صحة المستخدم، اعرض طلبًا بالمقاييس الحيوية (أو طريقة تأكيد أخرى). يستخدم مقتطف الرمز أدناه واجهة برمجة التطبيقات Android Biometric API.

  7. بعد نجاح المصادقة، أنشِئ استجابة JSON استنادًا إلى مواصفات W3 Web Authentication Assertion. وفي مقتطف الرمز أدناه، تُستخدَم فئات البيانات المساعدة، مثل AuthenticatorAssertionResponse، لأخذ المَعلمات المنظَّمة وتحويلها إلى تنسيق JSON المطلوب. وتحتوي الاستجابة على توقيع رقمي من المفتاح الخاص لبيانات اعتماد WebAuthn. يمكن لخادم الجهة المعتمدة التحقق من هذا التوقيع لمصادقة مستخدم قبل تسجيل الدخول.

  8. أنشِئ PublicKeyCredential باستخدام JSON الذي تم إنشاؤه أعلاه، واضبطه على GetCredentialResponse نهائية. قم بتعيين هذا الرد النهائي على نتيجة هذا النشاط.

يوضّح المثال التالي كيفية تنفيذ هذه الخطوات:

val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)

val biometricPrompt = BiometricPrompt(
    this,
    <executor>,
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationError(
        errorCode: Int, errString: CharSequence
        ) {
            super.onAuthenticationError(errorCode, errString)
            finish()
        }

        override fun onAuthenticationFailed() {
            super.onAuthenticationFailed()
            finish()
        }

        override fun onAuthenticationSucceeded(
        result: BiometricPrompt.AuthenticationResult
        ) {
        super.onAuthenticationSucceeded(result)
        val response = AuthenticatorAssertionResponse(
            requestOptions = request,
            credentialId = credId,
            origin = origin,
            up = true,
            uv = true,
            be = true,
            bs = true,
            userHandle = uid,
            packageName = packageName
        )

        val sig = Signature.getInstance("SHA256withECDSA");
        sig.initSign(privateKey)
        sig.update(response.dataToSign())
        response.signature = sig.sign()

        val credential = FidoPublicKeyCredential(
            rawId = credId, response = response
        )
        val result = Intent()
        val passkeyCredential = PublicKeyCredential(credential.json)
        PendingIntentHandler.setGetCredentialResponse(
            result, GetCredentialResponse(passkeyCredential)
        )
        setResult(RESULT_OK, result)
        finish()
        }
    }
)

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Use your screen lock")
    .setSubtitle("Use passkey for ${request.rpId}")
    .setAllowedAuthenticators(
            BiometricManager.Authenticators.BIOMETRIC_STRONG
            /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
        )
    .build()
biometricPrompt.authenticate(promptInfo)

التعامل مع اختيار المستخدم لمصادقة كلمة المرور

  1. في النشاط المقابل، عليك الوصول إلى الغرض الذي تم تمريره إلى onCreate واستخراج ProviderGetCredentialRequest باستخدام PendingIntentHandler.
  2. استخدِم GetPasswordOption في الطلب لاسترداد بيانات اعتماد كلمة المرور لاسم الحزمة الواردة.

    val getRequest =
    PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    
    val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption
    
    val username = passwordOption.username
    // Fetch the credentials for the calling app package name
    val creds = <your_database>.getCredentials(callingAppInfo.packageName)
    val passwords = creds.passwords
    val it = passwords.iterator()
    var password = ""
    while (it.hasNext() == true) {
        val passwordItemCurrent = it.next()
        if (passwordItemCurrent.username == username) {
           password = passwordItemCurrent.password
           break
        }
    }
    
  3. بعد استرداد بيانات اعتماد كلمة المرور المحدّدة، اضبط الاستجابة.

    // Set the response back
    val result = Intent()
    val passwordCredential = PasswordCredential(username, password)
    PendingIntentHandler.setGetCredentialResponse(
    result, GetCredentialResponse(passwordCredential)
    )
    setResult(Activity.RESULT_OK, result)
    finish()
    

التعامل مع اختيار إدخال إجراء المصادقة

كما ذكرنا سابقًا، يمكن لموفّر بيانات الاعتماد ضبط AuthenticationAction إذا كانت بيانات الاعتماد مقفلة. إذا اختار المستخدم هذا الإدخال، يتم استدعاء النشاط المقابل لمجموعة إجراءات الغرض في PendingIntent. ويمكن بعد ذلك عرض مسار مصادقة باستخدام المقاييس الحيوية أو آلية مشابهة لفتح بيانات الاعتماد. بعد نجاح العملية، على موفِّر بيانات الاعتماد إنشاء BeginGetCredentialResponse، على غرار الطريقة الموضَّحة أعلاه، وذلك لأنّه تم فتح قفل بيانات الاعتماد. يجب بعد ذلك ضبط هذه الاستجابة من خلال طريقة PendingIntentHandler.setBeginGetCredentialResponse() قبل ضبط القصد المعدّ على النتيجة وانتهاء النشاط.

محو طلبات بيانات الاعتماد

قد يطلب أحد تطبيقات العميل محو أي حالة تم الاحتفاظ بها لاختيار بيانات الاعتماد، على سبيل المثال، قد يتذكر موفِّر بيانات الاعتماد بيانات الاعتماد التي تم اختيارها سابقًا ولا يعرضها إلا في المرة القادمة. يطلب تطبيق العميل واجهة برمجة التطبيقات هذه ويتوقع محو التحديد الثابت. يمكن لخدمة موفِّر بيانات الاعتماد معالجة هذا الطلب من خلال تجاوز طريقة onClearCredentialStateRequest():

override fun onClearCredentialStateRequest(
    request: android.service.credentials.ClearCredentialStateRequest,
    cancellationSignal: CancellationSignal,
    callback: OutcomeReceiver<Void?, ClearCredentialException>,
  ) {
    // Delete any maintained state as appropriate.
}

الحصول على قائمة مسموح بها للتطبيقات المميّزة

تُجري التطبيقات المميّزة، مثل متصفِّحات الويب، طلبات "مدير بيانات الاعتماد" نيابةً عن جهات أخرى معتمَدة من خلال ضبط مَعلمة origin في طريقتَي "مدير بيانات الاعتماد" GetCredentialRequest() و CreatePublicKeyCredentialRequest(). لمعالجة هذه الطلبات، يسترد موفِّر بيانات الاعتماد origin باستخدام واجهة برمجة التطبيقات getOrigin().

لاسترداد origin، يحتاج تطبيق موفر بيانات الاعتماد إلى تمرير قائمة بالمتصلين المتميزين والموثوقين إلى androidx.credentials.provider.CallingAppInfo's getOrigin() API. يجب أن تكون هذه القائمة المسموح بها كائن JSON صالحًا. يتم عرض origin إذا كانت packageName والملفات المرجعية للشهادة التي تم الحصول عليها من signingInfo تتطابق مع ملفات التطبيق التي تم العثور عليها في privilegedAllowlist التي تم تمريرها إلى واجهة برمجة تطبيقات getOrigin(). بعد الحصول على القيمة origin، يجب أن يعتبر تطبيق موفّر الخدمة هذا الطلب كطلب مميّز وأن يضبط origin على بيانات العميل في AuthenticatorResponse، بدلاً من حساب origin باستخدام توقيع تطبيق الاتصال.

في حال استرداد origin، يمكنك استخدام عنصر clientDataHash المتوفّر مباشرةً في CreatePublicKeyCredentialRequest() أو GetPublicKeyCredentialOption() بدلاً من التجميع والتجزئةclientDataJSON أثناء طلب التوقيع. لتجنّب مشاكل تحليل JSON، يمكنك ضبط قيمة عنصر نائب لـ clientDataJSON في استجابة المصادقة والتأكيد.

يستخدم "مدير كلمات المرور في Google" قائمة مسموح بها متاحة بشكل علني للمكالمات الواردة إلى getOrigin(). وبصفتك موفِّر بيانات اعتماد، يمكنك استخدام هذه القائمة أو تقديم قائمة خاصة بك بتنسيق JSON الموضح في واجهة برمجة التطبيقات. ويعتمد تحديد القائمة التي يتم استخدامها على الموفّر. للحصول على إمكانية الوصول بامتياز مع موفري بيانات الاعتماد التابعين لجهات خارجية، يُرجى الرجوع إلى الوثائق التي تقدمها الجهة الخارجية.

تفعيل مقدّمي الخدمات على أحد الأجهزة

على المستخدمين تفعيل مقدّم الخدمة من خلال إعدادات الجهاز > كلمات المرور والحسابات > مقدِّم الخدمة > تفعيل أو إيقاف.

في نظام التشغيل Android 14 أو الإصدارات الأحدث، اطلب واجهة برمجة التطبيقات createSettingsPendingIntent() لعرض هدف في انتظار المراجعة عند استدعاؤه، ما يؤدي إلى عرض شاشة تسمح للمستخدم بتفعيل موفِّر بيانات الاعتماد.

fun createSettingsPendingIntent(): PendingIntent