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

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

راه‌اندازی

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

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

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

کاتلین

dependencies {
    implementation("androidx.credentials:credentials:1.6.0-beta03")
}

شیار

dependencies {
    implementation "androidx.credentials:credentials:1.6.0-beta03"
}

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

در فایل مانیفست برنامه خود 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>

مجوز و فیلتر intent که در مثال قبلی نشان داده شده است، برای عملکرد صحیح Credential Manager ضروری هستند. این مجوز مورد نیاز است تا فقط سیستم اندروید بتواند به این سرویس متصل شود. فیلتر intent برای قابلیت کشف این سرویس به عنوان یک ارائه دهنده اعتبارنامه که توسط 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>

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

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

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

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

مدیریت ایجاد رمز عبور

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

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

  1. متد onBeginCreateCredentialRequest() را در سرویس خود که از CredentialProviderService توسعه داده شده است، بازنویسی کنید.
  2. با ساخت یک BeginCreateCredentialResponse متناظر و ارسال آن از طریق تابع فراخوانی، 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 شما باید موارد زیر را رعایت کند:

  • فعالیت مربوطه باید طوری تنظیم شود که هرگونه درخواست، تأیید یا انتخاب بیومتریک مورد نیاز را نشان دهد.
  • هر داده‌ی مورد نیازی که ارائه‌دهنده هنگام فراخوانی اکتیویتی مربوطه به آن نیاز دارد، باید به عنوان یک مقدار اضافی روی intent که برای ایجاد PendingIntent شما استفاده می‌شود، مانند یک accountId در جریان ایجاد، تنظیم شود.
  • PendingIntent شما باید با پرچم PendingIntent.FLAG_MUTABLE ساخته شود تا سیستم بتواند درخواست نهایی را به intent extra اضافه کند.
  • PendingIntent شما نباید با پرچم PendingIntent.FLAG_ONE_SHOT ساخته شود، زیرا کاربر ممکن است یک ورودی را انتخاب کند، به عقب برگردد و دوباره آن را انتخاب کند که باعث می‌شود PendingIntent دو بار اجرا شود.
  • PendingIntent شما باید با یک کد درخواست منحصر به فرد ساخته شود تا هر ورودی بتواند PendingIntent مربوط به خود را داشته باشد.

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

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

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

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() که در بخش قبلی به آن اشاره شد، یک case دیگر درون بلوک switch برای مدیریت درخواست‌های رمز عبور اضافه کنید.
  • هنگام ساخت 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 مربوطه اجرا می‌شود و Activity مرتبط را نمایش می‌دهد. به intent مرتبط ارسال شده در onCreate دسترسی پیدا کنید و آن را به کلاس PendingIntentHander ارسال کنید تا متد ProviderCreateCredentialRequest را دریافت کنید.

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

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 را آماده می‌کند.
  • چارچوب اندروید با اتصال به این سرویس‌ها، این درخواست را به تمام ارائه‌دهندگان اعتبارنامه‌ی مربوطه ارسال می‌کند.
  • سپس سرویس ارائه دهنده یک 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 نیاز به باز کردن قفل اعتبارنامه‌ها دارند، باید یک intent در حال انتظار تنظیم کنند که کاربر را به جریان باز کردن قفل برنامه هدایت کند:

    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 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 مربوط به Activity مربوطه، intent مرتبط را بازیابی کنید و به 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. برای اعتبارسنجی کاربر، یک Biometric prompt (یا روش assertion دیگری) را نمایش دهید. قطعه کد زیر از Android Biometric API استفاده می‌کند.

  7. پس از موفقیت احراز هویت، یک پاسخ JSON بر اساس مشخصات W3 Web Authentication Assertion بسازید. در قطعه کد زیر، از کلاس‌های داده کمکی مانند 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. در اکتیویتی مربوطه، به intent ارسال شده به onCreate دسترسی پیدا کنید و با استفاده از PendingIntentHandler ProviderGetCredentialRequest استخراج کنید.
  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 تنظیم کند. اگر کاربر این ورودی را انتخاب کند، Activity مربوط به اکشن intent تنظیم شده در PendingIntent فراخوانی می‌شود. سپس ارائه‌دهندگان اعتبارنامه می‌توانند یک جریان احراز هویت بیومتریک یا مکانیسم مشابه را برای باز کردن قفل اعتبارنامه‌ها ارائه دهند. در صورت موفقیت، ارائه‌دهنده اعتبارنامه باید یک BeginGetCredentialResponse بسازد، مشابه نحوه مدیریت ورود کاربر که در بالا توضیح داده شد ، زیرا اعتبارنامه‌ها اکنون قفل شده‌اند. این پاسخ باید قبل از اینکه intent آماده شده به عنوان نتیجه تنظیم شود و Activity پایان یابد، از طریق متد PendingIntentHandler.setBeginGetCredentialResponse() تنظیم شود.

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

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

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

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

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

تنظیمات intents

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

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

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

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

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

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

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

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

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

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

fun createSettingsPendingIntent(): PendingIntent