Credential Manager را با راه حل ارائه دهنده اعتبار خود ادغام کنید

Credential Manager به مجموعه‌ای از APIهای معرفی‌شده در Android 14 اشاره دارد که از روش‌های ورود به سیستم متعدد مانند نام کاربری-گذرواژه، کلیدهای عبور و راه‌حل‌های ورود به سیستم فدرال (مانند ورود با Google) پشتیبانی می‌کنند. هنگامی که Credential Manager API فراخوانی می شود، سیستم Android اعتبارنامه ها را از همه ارائه دهندگان اعتبار نصب شده در دستگاه جمع می کند. این سند مجموعه ای از API ها را توصیف می کند که نقاط پایانی یکپارچه سازی را برای این ارائه دهندگان اعتبار ارائه می کنند.

راه اندازی

قبل از اجرای عملکرد در ارائه دهنده اعتبار خود، مراحل راه اندازی نشان داده شده در بخش های زیر را کامل کنید.

وابستگی ها را اعلام کنید

در فایل build.gradle ماژول خود، یک وابستگی را با استفاده از آخرین نسخه کتابخانه Credential Manager اعلام کنید:

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>

مجوز و فیلتر قصد نشان داده شده در بالا برای جریان مدیر اعتبارنامه برای کار همانطور که انتظار می رود ضروری است. مجوز لازم است تا فقط سیستم اندروید بتواند به این سرویس متصل شود. فیلتر قصد برای شناسایی این سرویس به عنوان یک ارائه دهنده اعتبار برای استفاده توسط Credential Manager استفاده می شود.

انواع اعتبارنامه های پشتیبانی شده را اعلام کنید

در پوشه 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>

در سطوح قبلی API، ارائه‌دهندگان اعتبار با APIهایی مانند تکمیل خودکار برای گذرواژه‌ها و سایر داده‌ها ادغام می‌شوند. این ارائه دهندگان می توانند از همان زیرساخت داخلی برای ذخیره انواع اعتبارنامه های موجود استفاده کنند، در حالی که آن را برای پشتیبانی از سایرین، از جمله کلیدهای عبور، گسترش دهند.

رویکرد دو مرحله ای برای تعامل با ارائه دهنده

مدیر اعتبار با ارائه دهندگان اعتبار در دو مرحله تعامل دارد:

  1. مرحله اول مرحله شروع/پرس و جو است که در آن سیستم به خدمات ارائه دهنده اعتبار متصل می شود و متدهای onBeginGetCredentialRequest() , onBeginCreateCredentialRequest() یا onClearCredentialStateRequest() را با درخواست های Begin… فراخوانی می کند. ارائه‌دهندگان باید این درخواست‌ها را پردازش کنند و با پاسخ‌های Begin… پاسخ دهند و آنها را با ورودی‌هایی پر کنند که نشان‌دهنده گزینه‌های بصری برای نمایش در انتخابگر حساب هستند. هر ورودی باید یک مجموعه PendingIntent داشته باشد.
  2. هنگامی که کاربر یک ورودی را انتخاب کرد، مرحله انتخاب شروع می شود و PendingIntent مرتبط با ورودی فعال می شود و فعالیت ارائه دهنده مربوطه را نشان می دهد. هنگامی که کاربر تعامل با این فعالیت را تمام کرد، ارائه دهنده اعتبار باید قبل از پایان فعالیت، پاسخ را به نتیجه فعالیت تنظیم کند. سپس این پاسخ به برنامه مشتری که Credential Manager را فراخوانی کرده است ارسال می شود.

ایجاد کلید عبور را مدیریت کنید

رسیدگی به پرس و جوها برای ایجاد رمز عبور

هنگامی که یک برنامه مشتری می خواهد یک رمز عبور ایجاد کند و آن را با یک ارائه دهنده اعتبار ذخیره کند، createCredential API را صدا می کند. برای رسیدگی به این درخواست در سرویس ارائه دهنده اعتبار خود به گونه ای که رمز عبور واقعاً در فضای ذخیره سازی شما ذخیره می شود، مراحل نشان داده شده در بخش های زیر را کامل کنید.

  1. روش onBeginCreateCredentialRequest() در سرویس خود که از CredentialProviderService توسعه داده شده است، لغو کنید.
  2. با ساختن یک BeginCreateCredentialResponse مربوطه و ارسال آن از طریق callback، BeginCreateCredentialRequest را مدیریت کنید.
  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 ساخته شود تا سیستم بتواند درخواست نهایی را به intent اضافه کند.
  • PendingIntent شما نباید با پرچم PendingIntent.FLAG_ONE_SHOT ساخته شود.FLAG_ONE_SHOT زیرا کاربر ممکن است یک ورودی را انتخاب کند، به عقب برگردد و آن را دوباره انتخاب کند که باعث می شود PendingIntent دو بار فعال شود.
  • PendingIntent شما باید با یک کد درخواست منحصر به فرد ساخته شود تا هر ورودی بتواند PendingIntent مربوط به خود را داشته باشد.

انتخاب ورودی برای درخواست‌های ایجاد رمز عبور را مدیریت کنید

  1. هنگامی که کاربر یک CreateEntry را انتخاب می کند که قبلاً پر شده است، PendingIntent مربوطه فراخوانی می شود و Activity ارائه دهنده مرتبط ایجاد می شود.
  2. پس از فراخوانی متد onCreate از Activity شما، به intent مرتبط دسترسی پیدا کنید و آن را به کلاس PendingIntentHander ارسال کنید تا ProviderCreateCredentialRequest را دریافت کنید.
  3. requestJson ، callingAppInfo و clientDataHash از درخواست استخراج کنید.
  4. accountId محلی را از intent extra استخراج کنید. این یک نمونه اجرای خاص برنامه است و مورد نیاز نیست. این شناسه حساب می تواند برای ذخیره این اعتبار در برابر این شناسه حساب خاص استفاده شود.
  5. requestJson را اعتبارسنجی کنید. مثال زیر از کلاس‌های داده محلی مانند PublicKeyCredentialCreationOptions برای تبدیل JSON ورودی به یک کلاس ساختاریافته طبق مشخصات WebAuthn استفاده می‌کند. به عنوان یک ارائه دهنده اعتبار، می توانید آن را با تجزیه کننده خود جایگزین کنید.
  6. اگر تماس از یک برنامه Android بومی است ، پیوند دارایی را برای برنامه تماس بررسی کنید.
  7. یک درخواست احراز هویت را در سطح قرار دهید. مثال زیر از Android Biometric API استفاده می کند.
  8. وقتی احراز هویت با موفقیت انجام شد، یک credentialId و یک جفت کلید ایجاد کنید.
  9. کلید خصوصی را در پایگاه داده محلی خود در برابر callingAppInfo.packageName ذخیره کنید.
  10. یک پاسخ JSON API Authentication Web بسازید که از کلید عمومی و credentialId تشکیل شده است. مثال زیر از کلاس های ابزار محلی مانند AuthenticatorAttestationResponse و FidoPublicKeyCredential استفاده می کند که به ساخت JSON بر اساس مشخصات ذکر شده قبلی کمک می کند. به عنوان یک ارائه دهنده اعتبار، می توانید این کلاس ها را با سازنده های خود جایگزین کنید.
  11. با JSON تولید شده در بالا یک CreatePublicKeyCredentialResponse بسازید.
  12. از طریق PendingIntentHander.setCreateCredentialResponse() CreatePublicKeyCredentialResponse به عنوان یک Intent اضافه کنید و آن را روی نتیجه Activity تنظیم کنید.
  13. فعالیت را تمام کنید.

مثال کد زیر این مراحل را نشان می دهد. این کد پس از فراخوانی onCreate() باید در کلاس Activity شما مدیریت شود.

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 مربوطه، Activity مرتبط را اجرا کرده و نمایش می دهد. به intent مرتبط ارسال شده در onCreate دسترسی پیدا کنید و آن را به کلاس PendingIntentHander ارسال کنید تا متد ProviderCreateCredentialRequest را دریافت کنید.

مثال زیر نحوه اجرای این فرآیند را نشان می دهد. این کد باید در متد onCreate() Activity شما مدیریت شود.

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 به‌عنوان یک مورد اضافی در intent تنظیم کنید تا بدانید وقتی کاربر این ورودی را انتخاب می‌کند به کدام اعتبار نگاشت می‌شود.

    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 از Activity مربوطه، intent مرتبط را بازیابی کنید و به PendingIntentHandler.retrieveProviderGetCredentialRequest() منتقل کنید.
  2. GetPublicKeyCredentialOption را از درخواست بازیابی شده در بالا استخراج کنید. سپس requestJson و clientDataHash را از این گزینه استخراج کنید.
  3. credentialId از intent extra، که توسط ارائه‌دهنده اعتبار زمانی که 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. در فعالیت مربوطه خود، به intent ارسال شده برای 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 تنظیم کند. اگر کاربر این ورودی را انتخاب کند، Activity مربوط به مجموعه اقدام قصد در PendingIntent فراخوانی می شود. سپس ارائه دهندگان اعتبارنامه می توانند یک جریان احراز هویت بیومتریک یا مکانیسم مشابهی را برای باز کردن قفل اعتبارنامه ها ارائه کنند. در صورت موفقیت، ارائه‌دهنده اعتبار باید یک BeginGetCredentialResponse بسازد، مشابه نحوه مدیریت ورود به سیستم کاربر در بالا ، زیرا اعتبارنامه‌ها اکنون باز شده‌اند. سپس این پاسخ باید از طریق متد PendingIntentHandler.setBeginGetCredentialResponse() تنظیم شود قبل از اینکه هدف آماده شده به عنوان نتیجه تنظیم شود و فعالیت تمام شود.

درخواست های اعتبارنامه را پاک کنید

یک برنامه مشتری ممکن است درخواست کند که هر حالتی که برای انتخاب اعتبار حفظ می شود باید پاک شود، مانند ارائه دهنده اعتبارنامه ممکن است اعتبار انتخاب شده قبلی را به خاطر بسپارد و فقط دفعه بعد آن را برگرداند. یک برنامه مشتری این API را فراخوانی می کند و انتظار دارد که انتخاب چسبنده پاک شود. سرویس ارائه‌دهنده اعتبار شما می‌تواند این درخواست را با نادیده گرفتن روش onClearCredentialStateRequest() رسیدگی کند:

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

برای اینکه به کاربرانتان اجازه دهید تنظیمات ارائه‌دهنده شما را از صفحه گذرواژه‌ها، کلیدهای عبور و تکمیل خودکار باز کنند، برنامه‌های ارائه‌دهنده اعتبار باید ویژگی settingsActivity credential-provider در res/xml/provider.xml پیاده‌سازی کنند. اگر کاربر روی نام ارائه‌دهنده در لیست گذرواژه‌ها، کلیدهای عبور و تکمیل خودکار سرویس‌ها کلیک کند، این ویژگی به شما امکان می‌دهد از یک قصد برای باز کردن صفحه تنظیمات برنامه خود استفاده کنید. مقدار این ویژگی را به نام فعالیتی که از صفحه تنظیمات راه اندازی می شود، تنظیم کنید.

<credential-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsSubtitle="Example settings provider name"
    android:settingsActivity="com.example.SettingsActivity">
    <capabilities>
        <capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
    </capabilities>
</credential-provider>
نموداری که عملکرد دکمه های تغییر و باز را نشان می دهد
شکل 1: دکمه Change گفتگوی انتخاب موجود را باز می کند و به کاربر امکان می دهد ارائه دهنده اعتبار مورد نظر خود را انتخاب کند. دکمه Open فعالیت تنظیمات تعریف شده در تغییر مانیفست را راه اندازی می کند و صفحه تنظیمات را به طور خاص برای آن ارائه دهنده باز می کند.

اهداف تنظیمات

تنظیمات باز : هدف android.settings.CREDENTIAL_PROVIDER صفحه تنظیماتی را نشان می دهد که در آن کاربر می تواند ارائه دهندگان اعتبار مورد نظر و اضافی خود را انتخاب کند.

صفحه تنظیمات رمزهای عبور، کلیدهای عبور و تکمیل خودکار
شکل 2: صفحه تنظیمات رمزهای عبور، کلیدهای عبور و تکمیل خودکار.

سرویس اعتبار ترجیحی : هدف ACTION_REQUEST_SET_AUTOFILL_SERVICE کاربر شما را به صفحه انتخاب ارائه دهنده ترجیحی هدایت می کند. ارائه‌دهنده انتخاب‌شده در این صفحه به ارائه‌دهنده اعتبار و تکمیل خودکار ترجیحی تبدیل می‌شود.

نموداری که عملکرد دکمه های تغییر و باز را نشان می دهد
شکل 3: سرویس ترجیحی برای گذرواژه، کلیدهای عبور و صفحه تنظیمات تکمیل خودکار.

یک لیست مجاز از برنامه های دارای امتیاز دریافت کنید

برنامه‌های دارای امتیاز مانند مرورگرهای وب با تنظیم پارامتر origin در متدهای Credential Manager GetCredentialRequest() و CreatePublicKeyCredentialRequest() تماس‌های Credential Manager را از طرف دیگر طرف‌های متکی انجام می‌دهند. برای پردازش این درخواست‌ها، ارائه‌دهنده اعتبار origin را با استفاده از getOrigin() API بازیابی می‌کند.

برای بازیابی origin ، برنامه ارائه‌دهنده اعتبار باید فهرستی از تماس‌گیرندگان ممتاز و قابل اعتماد را به API androidx.credentials.provider.CallingAppInfo's getOrigin() ارسال کند. این لیست مجاز باید یک شی JSON معتبر باشد. در صورتی که اثر انگشت packageName و گواهینامه به دست آمده از signingInfo با برنامه موجود در privilegedAllowlist ارسال شده به API getOrigin() مطابقت داشته باشد، origin برگردانده می شود. پس از به دست آمدن مقدار origin ، برنامه ارائه دهنده باید این را یک تماس ممتاز در نظر بگیرد و به جای محاسبه origin با استفاده از امضای برنامه تماس، این origin روی داده های سرویس گیرنده در AuthenticatorResponse تنظیم کند.

اگر origin بازیابی می‌کنید، از clientDataHash که مستقیماً در CreatePublicKeyCredentialRequest() یا GetPublicKeyCredentialOption() ارائه شده است، به جای مونتاژ و هش کردن clientDataJSON در طول درخواست امضا استفاده کنید. برای جلوگیری از مشکلات تجزیه JSON، یک مقدار جای نگهدار برای clientDataJSON در پاسخ گواه و ادعا تنظیم کنید. Google Password Manager از یک لیست مجاز در دسترس برای تماس با getOrigin() استفاده می کند. به‌عنوان یک ارائه‌دهنده اعتبار، می‌توانید از این فهرست استفاده کنید یا فهرست خود را در قالب JSON که توسط API توصیف شده است ارائه دهید. انتخاب لیست مورد استفاده به عهده ارائه دهنده است. برای دسترسی ممتاز به ارائه دهندگان اعتبار شخص ثالث، به اسناد ارائه شده توسط شخص ثالث مراجعه کنید.

ارائه دهندگان را در یک دستگاه فعال کنید

کاربران باید از طریق تنظیمات دستگاه > گذرواژه‌ها و حساب‌ها > ارائه‌دهنده شما > فعال یا غیرفعال کردن ارائه‌دهنده را فعال کنند.

fun createSettingsPendingIntent(): PendingIntent