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ساخته شود تا سیستم بتواند درخواست نهایی را به intent extra اضافه کند.PendingIntentشما نباید با پرچمPendingIntent.FLAG_ONE_SHOTساخته شود زیرا ممکن است کاربر ورودیای را انتخاب کند، برگردد و دوباره آن را انتخاب کند که باعث میشودPendingIntentدو بار فعال شود.PendingIntentشما باید با کد درخواست منحصربهفردی ساخته شود تا هر ورودی بتواندPendingIntentمتناظر خودش را داشته باشد.
مدیریت انتخاب ورودی برای درخواستهای ایجاد گذرکلید
- وقتی کاربر
CreateEntryازپیش تکمیلشدهای را انتخاب میکند،PendingIntentمربوطه فراخوانده میشود و ارائهدهنده مرتبطActivityایجاد میشود. - پساز فراخوانی روش
onCreate«فعالیت» شما، به قصد مرتبط دسترسی پیدا کنید و آن را به کلاسPendingIntentHandlerمنتقل کنید تاProviderCreateCredentialRequestرا دریافت کنید. -
requestJson،callingAppInfo، وclientDataHashرا از درخواست استخراج کنید. accountIdمحلی را از قصد اضافی استخراج کنید. این یک پیادهسازی خاص برنامه نمونه است و الزامی نیست. از این شناسه حساب میتوان برای ذخیره کردن این اعتبارنامه درمقابل این شناسه حساب خاص استفاده کرد.requestJsonرا اعتبارسنجی کنید. نمونه زیر از کلاسهای داده محلی مانندPublicKeyCredentialCreationOptionsبرای تبدیل JSON ورودی به کلاس ساختاریافته طبق مشخصات WebAuthn استفاده میکند. بهعنوان ارائهدهنده اطلاعات کاربری، میتوانید این را با تجزیهکننده خودتان جایگزین کنید.- اگر تماس از برنامه Android بومی برقرار میشود، پیوند دارایی را برای برنامه تماسگیرنده بررسی کنید.
- پیامواره اصالتسنجی را نمایش دهید. مثال زیر از میانای برنامهسازی کاربردی Biometric Android استفاده میکند.
- وقتی اصالتسنجی موفقیتآمیز بود،
credentialIdو جفت کلید تولید کنید. - کلید خصوصی را در پایگاه داده محلی خود در مقابل
callingAppInfo.packageNameذخیره کنید. - پاسخ JSON میانای برنامهسازی کاربردی اصالتسنجی وب را که شامل کلید عمومی و
credentialIdاست بسازید. مثال زیر از کلاسهای سودمند محلی مانندAuthenticatorAttestationResponseوFidoPublicKeyCredentialاستفاده میکند که به ساخت JSON براساس مشخصات ذکرشده قبلی کمک میکنند. بهعنوان ارائهدهنده اطلاعات کاربری، میتوانید این کلاسها را با سازندگان خودتان جایگزین کنید. - با JSON تولیدشده در مرحله قبل،
CreatePublicKeyCredentialResponseبسازید. CreatePublicKeyCredentialResponseرا بهعنوان یک مورد اضافی درIntentازطریقPendingIntentHandler.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 ارسال شده است دسترسی پیدا کنید و آن را به کلاس PendingIntentHandler ارسال کنید تا روش 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