ادغام کردن «مدیر اطلاعات کاربری» با راهکار ارائه‌دهنده اطلاعات کاربری شما

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

راه‌اندازی

قبل‌از پیاده‌سازی عملکرد در ارائه‌دهنده اطلاعات کاربری، مراحل راه‌اندازی نشان‌داده‌شده در بخش‌های زیر را تکمیل کنید.

اعلام وابستگی‌ها

برای استفاده از جدیدترین نسخه کتابخانه Credential Manager، وابستگی‌های زیر را به اسکریپت ساخت ماژول برنامه‌تان اضافه کنید:

کاتلین

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

شیار

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

اعلام عنصر سرویس در فایل مانیفست

در فایل مانیفست برنامه‌تان AndroidManifest.xml، یک اعلامیه <service> برای کلاس سرویسی که کلاس CredentialProviderService را از کتابخانه androidx.credentials گسترش می‌دهد اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است.

<service android:name=".MyCredentialProviderService"
    android:enabled="true"
    android:exported="true"
    android:label="My Credential Provider"
    android:icon="@mipmap/ic_launcher"
    android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
    tools:targetApi="upside_down_cake">
    <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 طبق انتظار کار کند ضروری است. این اجازه لازم است تا فقط سیستم Android بتواند به این سرویس متصل شود. فیلتر قصد برای قابل‌کشف بودن این سرویس به‌عنوان ارائه‌دهنده اطلاعات کاربری استفاده می‌شود تا Credential Manager بتواند از آن استفاده کند.

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

در فهرست راهنمای res/xml، فایل جدیدی به‌نام provider.xml ایجاد کنید. در این فایل، انواع اطلاعات اعتباری را که سرویس شما پشتیبانی می‌کند، ازطریق ثابت‌های تعریف‌شده برای هر نوع اطلاعات اعتباری در کتابخانه اعلام کنید. در مثال زیر، سرویس از گذرواژه‌های سنتی و همچنین گذرکلیدها پشتیبانی می‌کند، ثابت‌های مربوط به آن‌ها به‌صورت TYPE_PASSWORD_CREDENTIAL و TYPE_PUBLIC_KEY_CREDENTIAL تعریف شده‌اند:

<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>

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

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

‫Credential Manager در دو مرحله با ارائه‌دهندگان اطلاعات کاربری تعامل می‌کند:

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

مدیریت ایجاد گذرکلید

رسیدگی به پُرسمان‌های ایجاد گذرکلید

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

  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 محلی را از intent extra استخراج کنید. این یک پیاده‌سازی خاص برنامه نمونه است و الزامی نیست. از این شناسه حساب می‌توان برای ذخیره این اعتبارنامه درمقابل این شناسه حساب خاص استفاده کرد.
  5. requestJson را اعتبارسنجی کنید. نمونه زیر از کلاس‌های داده محلی مانند PublicKeyCredentialCreationOptions برای تبدیل JSON ورودی به کلاس ساختاریافته مطابق با مشخصات WebAuthn استفاده می‌کند. به‌عنوان ارائه‌دهنده اطلاعات کاربری، می‌توانید این را با تجزیه‌کننده خودتان جایگزین کنید.
  6. اگر تماس از برنامه Android بومی برقرار می‌شود، پیوند دارایی را برای برنامه تماس‌گیرنده بررسی کنید.
  7. پیام‌واره اصالت‌سنجی را نمایش دهید. مثال زیر از میانای برنامه‌سازی کاربردی Biometric Android استفاده می‌کند.
  8. وقتی اصالت‌سنجی موفقیت‌آمیز بود، یک credentialId و یک جفت کلید تولید کنید.
  9. کلید خصوصی را در پایگاه داده محلی خود دربرابر callingAppInfo.packageName ذخیره کنید.
  10. پاسخ JSON میانای برنامه‌سازی کاربردی «اصالت‌سنجی وب» را که شامل کلید عمومی و credentialId است بسازید. نمونه زیر از کلاس‌های سودمند محلی مثل AuthenticatorAttestationResponse و FidoPublicKeyCredential استفاده می‌کند که به ساخت JSON براساس مشخصات ذکرشده در بالا کمک می‌کنند.به‌عنوان ارائه‌دهنده اطلاعات کاربری، می‌توانید این کلاس‌ها را با سازندگان خودتان جایگزین کنید.
  11. با JSON تولیدشده در بالا، CreatePublicKeyCredentialResponse بسازید.
  12. CreatePublicKeyCredentialResponse را به‌عنوان یک مورد اضافی در Intent ازطریق PendingIntentHander.setCreateCredentialResponse() تنظیم کنید و آن هدف را روی نتیجه «فعالیت» تنظیم کنید.
  13. فعالیت را تمام کنید.

نمونه کد زیر این مراحل را نشان می‌دهد. این کد باید در کلاس «فعالیت» شما پس‌از فراخوانی onCreate() مدیریت شود.

override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
    super.onCreate(savedInstanceState, persistentState)
    // ...

    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
        )
    }
}

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

    val biometricPrompt = BiometricPrompt(
        this,
        { }, // Pass in your own executor
        object : AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                finish()
            }

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

            @RequiresApi(VERSION_CODES.P)
            override fun onAuthenticationSucceeded(
                result: 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,
                    authenticatorAttachment = "", // Add your authenticator attachment
                )
                val result = Intent()

                val createPublicKeyCredResponse =
                    CreatePublicKeyCredentialResponse(credential.json())

                // Set the CreateCredentialResponse as the result of the Activity
                PendingIntentHandler.setCreateCredentialResponse(
                    result,
                    createPublicKeyCredResponse
                )
                setResult(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)
}

@RequiresApi(VERSION_CODES.P)
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
}

@RequiresApi(VERSION_CODES.M)
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)

if (createRequest == null) {
    return
}

val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest

// Fetch the ID and password from the request and save it in your database
mDatabase.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)
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 processGetCredentialRequest(
        request: BeginGetCredentialRequest
    ): BeginGetCredentialResponse {
        val callingPackageInfo = request.callingAppInfo
        val callingPackageName = callingPackageInfo?.packageName.orEmpty()
        val credentialEntries: MutableList<CredentialEntry> = mutableListOf()
    
        for (option in request.beginGetCredentialOptions) {
            when (option) {
                is BeginGetPasswordOption -> {
                    credentialEntries.addAll(
                        populatePasswordData(
                            callingPackageName,
                            option
                        )
                    )
                }
                is BeginGetPublicKeyCredentialOption -> {
                    credentialEntries.addAll(
                        populatePasskeyData(
                            callingPackageInfo,
                            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
                    ),
                    beginGetPublicKeyCredentialOption = 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 extra استخراج کنید، که توسط ارائه‌دهنده اطلاعات کاربری هنگام راه‌اندازی PendingIntent مربوطه پر شده است.
  4. بااستفاده از پارامترهای درخواست که در بالا به آن‌ها دسترسی پیدا کردید، گذرکلید را از پایگاه داده محلی‌تان استخراج کنید.
  5. بااستفاده از فراداده استخراج‌شده و درستی‌سنجی کاربر، تأیید کنید که گذرکلید معتبر است.

    val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption
    
    val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
    val credIdEnc = requestInfo?.getString("credId").orEmpty()
    
    // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest
    val passkey = mDatabase.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 بسازید. در تکه‌کد زیر، کلاس‌های داده کمکی مانند AuthenticatorAssertionResponse برای دریافت پارامترهای ساختاریافته و تبدیل آن‌ها به قالب JSON موردنیاز استفاده می‌شوند. پاسخ حاوی امضای دیجیتال از کلید خصوصی اعتبارنامه WebAuthn است. سرور طرف متکی می‌تواند این امضا را درستی‌سنجی کند تا اصالت کاربر را پیش‌از ورود به سیستم تأیید کند.

  8. بااستفاده از JSON تولیدشده در بالا، PublicKeyCredential بسازید و آن را روی GetCredentialResponse نهایی تنظیم کنید. این پاسخ نهایی را روی نتیجه این فعالیت تنظیم کنید.

مثال زیر نشان می‌دهد که چگونه می‌توان این مراحل را اجرا کرد:

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

val biometricPrompt = BiometricPrompt(
    this,
    { }, // Pass in your own 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,
                authenticatorAttachment = "", // Add your authenticator attachment
            )
            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?.credentialOptions?.first() as GetPasswordOption
    
    val username = passwordOption.allowedUserIds.first()
    // Fetch the credentials for the calling app package name
    val creds = mDatabase.getCredentials(callingAppInfo.packageName)
    val passwords = creds.passwords
    val it = passwords.iterator()
    var password = ""
    while (it.hasNext()) {
        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: ProviderClearCredentialStateRequest,
    cancellationSignal: CancellationSignal,
    callback: OutcomeReceiver<Void?, ClearCredentialException>
) {
    // Delete any maintained state as appropriate.
}

برای اینکه کاربران بتوانند تنظیمات ارائه‌دهنده شما را از صفحه گذرواژه‌ها، گذرکلیدها، و تکمیل خودکار باز کنند، برنامه‌های ارائه‌دهنده اطلاعات کاربری باید مشخصه مانیفست credential-provider settingsActivity را در 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>
نموداری که عملکرد دکمه‌های تغییر و باز کردن را نشان می‌دهد
شکل ۱: دکمه تغییر کادر گفتگوی انتخاب موجود را باز می‌کند و به کاربر اجازه می‌دهد ارائه‌دهنده اطلاعات کاربری ترجیحی خود را انتخاب کند. دکمه باز کردن فعالیت تنظیمات تعریف‌شده در تغییر مانیفست را راه‌اندازی می‌کند و صفحه تنظیمات را به‌طور خاص برای آن ارائه‌دهنده باز می‌کند.

مقصودهای تنظیمات

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

صفحه تنظیمات «گذرواژه‌ها، گذرکلیدها، و تکمیل خودکار»
شکل ۲: صفحه تنظیمات «گذرواژه‌ها، گذرکلیدها، و تکمیل خودکار»

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

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

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

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

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

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

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

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

fun createSettingsPendingIntent(): PendingIntent