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