مدیریت اعتبارنامه (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هایی مانند تکمیل خودکار برای رمزهای عبور و سایر دادهها ادغام میشدند. این ارائهدهندگان میتوانند از همان زیرساخت داخلی برای ذخیره انواع اعتبارنامههای موجود استفاده کنند، در حالی که آن را برای پشتیبانی از انواع دیگر، از جمله کلیدهای عبور، گسترش میدهند.
رویکرد دو مرحلهای به تعامل با ارائهدهنده
مدیر اعتبارنامه در دو مرحله با ارائهدهندگان اعتبارنامه تعامل میکند:
- مرحله اول، مرحله شروع/پرسوجو است که در آن سیستم به سرویسهای ارائهدهنده اعتبارنامه متصل میشود و متدهای
onBeginGetCredentialRequest()،onBeginCreateCredentialRequest()یاonClearCredentialStateRequest()با درخواستهایBegin…فراخوانی میکند. ارائهدهندگان باید این درخواستها را پردازش کرده و با پاسخهایBegin…پاسخ دهند و آنها را با ورودیهایی پر کنند که نشاندهنده گزینههای بصری برای نمایش در انتخابگر حساب هستند. هر ورودی باید یک مجموعهPendingIntentداشته باشد. - به محض اینکه کاربر یک ورودی را انتخاب میکند، مرحله انتخاب آغاز میشود و
PendingIntentمرتبط با ورودی فعال میشود و activity مربوط به provider را اجرا میکند. به محض اینکه کاربر با این activity تعامل خود را تمام کرد، credential provider باید قبل از پایان دادن به activity، پاسخ را مطابق با نتیجه آن activity تنظیم کند. سپس این پاسخ به برنامه کلاینتی که Credential Manager را فراخوانی کرده است، ارسال میشود.
مدیریت ایجاد رمز عبور
مدیریت درخواستها برای ایجاد رمز عبور
وقتی یک برنامهی کلاینت میخواهد یک کلید عبور ایجاد کند و آن را در یک ارائهدهندهی اعتبارنامه ذخیره کند، API createCredential فراخوانی میکند. برای مدیریت این درخواست در سرویس ارائهدهندهی اعتبارنامهتان به گونهای که کلید عبور واقعاً در حافظهی شما ذخیره شود، مراحل نشان داده شده در بخشهای زیر را انجام دهید.
- متد
onBeginCreateCredentialRequest()را در سرویس خود که ازCredentialProviderServiceتوسعه داده شده است، بازنویسی کنید. - با ساخت یک
BeginCreateCredentialResponseمتناظر و ارسال آن از طریق تابع فراخوانی،BeginCreateCredentialRequestرا مدیریت کنید. - هنگام ساخت
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مربوط به خود را داشته باشد.
مدیریت انتخاب ورودی برای درخواستهای ایجاد رمز عبور
- وقتی کاربر
CreateEntryای که قبلاً پر شده است را انتخاب میکند،PendingIntentمربوطه فراخوانی میشود وActivityمربوط به provider ایجاد میشود. - پس از فراخوانی متد
onCreateمربوط به Activity شما، به intent مرتبط دسترسی پیدا کنید و آن را به کلاسPendingIntentHanderارسال کنید تاProviderCreateCredentialRequestرا دریافت کنید. -
requestJson،callingAppInfoوclientDataHashرا از درخواست استخراج کنید. -
accountIdمحلی را از intent extra استخراج کنید. این یک پیادهسازی نمونه مخصوص برنامه است و الزامی نیست. این شناسه حساب میتواند برای ذخیره این اعتبارنامه در مقابل این شناسه حساب خاص استفاده شود. - اعتبارسنجی
requestJson. مثال زیر از کلاسهای داده محلی مانندPublicKeyCredentialCreationOptionsبرای تبدیل JSON ورودی به یک کلاس ساختاریافته طبق مشخصات WebAuthn استفاده میکند. به عنوان یک ارائهدهنده اعتبارنامه، میتوانید این را با تجزیهکننده خودتان جایگزین کنید. - اگر تماس از یک برنامه بومی اندروید است، asset-link مربوط به برنامه تماس گیرنده را بررسی کنید.
- یک اعلان احراز هویت نمایش داده میشود. مثال زیر از API بیومتریک اندروید استفاده میکند.
- وقتی احراز هویت موفقیتآمیز بود، یک
credentialIdو یک جفت کلید ایجاد کنید. - کلید خصوصی را در پایگاه داده محلی خود در مقابل
callingAppInfo.packageNameذخیره کنید. - یک پاسخ JSON از API احراز هویت وب بسازید که شامل کلید عمومی و
credentialIdباشد. مثال زیر از کلاسهای کاربردی محلی مانندAuthenticatorAttestationResponseوFidoPublicKeyCredentialاستفاده میکند که به ساخت یک JSON بر اساس مشخصات ذکر شده قبلی کمک میکنند. به عنوان یک ارائه دهنده اعتبارنامه، میتوانید این کلاسها را با سازندگان خود جایگزین کنید. - با استفاده از JSON تولید شده در بالا، یک
CreatePublicKeyCredentialResponseبسازید. -
CreatePublicKeyCredentialResponseاز طریقPendingIntentHander.setCreateCredentialResponse()به عنوان یک تابع اضافی روی یکIntentتنظیم کنید و آن intent را روی نتیجه Activity تنظیم کنید. - فعالیت را تمام کنید.
مثال کد زیر این مراحل را نشان میدهد. این کد باید پس از فراخوانی 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است که هر کدام شامل پارامترهایی هستند که میتوانند برای بازیابی اعتبارنامههای منطبق استفاده شوند.
برای رسیدگی به این درخواست در سرویس ارائه دهنده اعتبارنامه خود، مراحل زیر را انجام دهید:
برای مدیریت درخواست، متد
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 ) ) }اعتبارنامهها را از پایگاه داده محلی خود بازیابی کنید و آنها را با استفاده از
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) }اعتبارنامهها را از پایگاه داده خود پرسوجو کنید، ورودیهای رمز عبور و کلید عبور را برای پر کردن ایجاد کنید.
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) ) }پس از اینکه اطلاعات احراز هویت را جستجو و وارد کردید، اکنون باید مرحله انتخاب اطلاعات احراز هویت انتخاب شده توسط کاربر را مدیریت کنید، چه کلید عبور باشد و چه رمز عبور.
مدیریت انتخاب کاربر برای کلیدهای عبور
- در متد
onCreateمربوط به Activity مربوطه، intent مرتبط را بازیابی کنید و بهPendingIntentHandler.retrieveProviderGetCredentialRequest()ارسال کنید. -
GetPublicKeyCredentialOptionرا از درخواست بازیابی شده در بالا استخراج کنید. متعاقباً،requestJsonوclientDataHashرا از این گزینه استخراج کنید. -
credentialIdرا از intent extra استخراج کنید، که توسط ارائه دهنده اعتبارنامه هنگام تنظیمPendingIntentمربوطه پر شده است. - با استفاده از پارامترهای درخواست که در بالا به آنها دسترسی پیدا کردید، کلید عبور را از پایگاه داده محلی خود استخراج کنید.
با استفاده از فرادادههای استخراجشده و تأیید کاربر، تأیید کنید که کلید عبور معتبر است.
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 )برای اعتبارسنجی کاربر، یک Biometric prompt (یا روش assertion دیگری) را نمایش دهید. قطعه کد زیر از Android Biometric API استفاده میکند.
پس از موفقیت احراز هویت، یک پاسخ JSON بر اساس مشخصات W3 Web Authentication Assertion بسازید. در قطعه کد زیر، از کلاسهای داده کمکی مانند
AuthenticatorAssertionResponseبرای دریافت پارامترهای ساختاریافته و تبدیل آنها به فرمت JSON مورد نیاز استفاده میشود. پاسخ شامل یک امضای دیجیتال از کلید خصوصی یک اعتبارنامه WebAuthn است. سرور طرف اعتمادکننده میتواند این امضا را برای احراز هویت کاربر قبل از ورود به سیستم تأیید کند.با استفاده از 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)
مدیریت انتخاب کاربر برای احراز هویت با رمز عبور
- در اکتیویتی مربوطه، به intent ارسال شده به
onCreateدسترسی پیدا کنید و با استفاده ازPendingIntentHandlerProviderGetCredentialRequestاستخراج کنید. از
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 } }پس از بازیابی، پاسخ را برای اعتبار رمز عبور انتخاب شده تنظیم کنید.
// 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