تشير واجهة Credential Manager إلى مجموعة من واجهات برمجة التطبيقات التي تم طرحها في Android 14 وتتيح استخدام طرق تسجيل دخول متعددة، مثل اسم المستخدم وكلمة المرور ومفاتيح المرور وحلول تسجيل الدخول الموحَّدة (مثل ميزة "تسجيل الدخول باستخدام حساب Google"). عند استدعاء واجهة برمجة التطبيقات Credential Manager API، يجمع نظام Android بيانات الاعتماد من جميع موفّري بيانات الاعتماد المثبَّتين على الجهاز. يصف هذا المستند مجموعة واجهات برمجة التطبيقات التي توفّر نقاط نهاية التكامل لموفّري بيانات الاعتماد هؤلاء.
الإعداد
قبل تنفيذ الوظائف في موفّر بيانات الاعتماد، أكمِل خطوات الإعداد الموضّحة في الأقسام التالية.
تحديد الاعتماديات
أضِف التبعيات التالية إلى نص إنشاء وحدة التطبيق لاستخدام أحدث إصدار من مكتبة Credential Manager:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" }
تعريف عنصر الخدمة في ملف البيان
في ملف بيان تطبيقك 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 فقط. يُستخدَم فلتر الأغراض لإتاحة اكتشاف هذه الخدمة كموفِّر بيانات اعتماد يستخدمه "مدير بيانات الاعتماد".
الإفصاح عن أنواع بيانات الاعتماد المتوافقة
في دليل 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. لمعالجة هذا الطلب في خدمة موفِّر بيانات الاعتماد بحيث يتم تخزين مفتاح المرور في مساحة التخزين، أكمِل الخطوات الموضّحة في الأقسام التالية.
- يمكنك إلغاء طريقة
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المحلي من البيانات الإضافية للطلب. هذا تنفيذ خاص بنموذج التطبيق وليس مطلوبًا. يمكن استخدام رقم تعريف الحساب هذا لتخزين بيانات الاعتماد هذه في رقم تعريف الحساب المحدّد هذا. - تحقَّق من صحة
requestJson. يستخدم المثال أدناه فئات البيانات المحلية، مثلPublicKeyCredentialCreationOptions، لتحويل ملف JSON الذي تم إدخاله إلى فئة منظَّمة وفقًا لمواصفات WebAuthn. بصفتك موفّر بيانات اعتماد، يمكنك استبدال ذلك بالمحلّل اللغوي الخاص بك. - تحقَّق من رابط مادة العرض لتطبيق الاتصال إذا كانت المكالمة واردة من تطبيق Android أصلي.
- عرض إشعار للمصادقة يستخدم المثال أدناه واجهة برمجة التطبيقات Biometric في Android.
- عند نجاح المصادقة، أنشئ
credentialIdومفتاحَي تشفير. - احفظ المفتاح الخاص في قاعدة البيانات المحلية مقابل
callingAppInfo.packageName. - أنشئ استجابة JSON لواجهة برمجة التطبيقات Web Authentication تتألف من المفتاح العام و
credentialId. يستخدم المثال أدناه فئات الأدوات المساعدة المحلية، مثلAuthenticatorAttestationResponseوFidoPublicKeyCredential، التي تساعد في إنشاء ملف JSON استنادًا إلى المواصفات المذكورة سابقًا.بصفتك مقدّم بيانات اعتماد، يمكنك استبدال هذه الفئات بأدوات الإنشاء الخاصة بك. - أنشئ
CreatePublicKeyCredentialResponseباستخدام ملف JSON الذي تم إنشاؤه أعلاه. - اضبط
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إعداد 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كإضافة على الغرض لمعرفة بيانات الاعتماد التي يتم ربطها بها عندما يختار المستخدم هذا الإدخال.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من البيانات الإضافية للطلب التي تم ملؤها من قِبل موفِّر بيانات الاعتماد عند إعداد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. ويمكن لخادم الجهة المعتمدة التحقّق من هذا التوقيع لمصادقة المستخدم قبل تسجيل الدخول.أنشئ
PublicKeyCredentialباستخدام ملف JSON الذي تم إنشاؤه أعلاه واضبطه على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 توجيه المستخدم إلى شاشة اختيار مقدّم الخدمة المفضّل. يصبح مقدّم الخدمة المحدّد في هذه الشاشة هو مقدّم الخدمة المفضّل لبيانات الاعتماد والملء التلقائي.
الحصول على قائمة مسموح بها للتطبيقات المميّزة
تُجري التطبيقات المميّزة، مثل متصفّحات الويب، طلبات إلى Credential Manager نيابةً عن
الأطراف المعتمِدة الأخرى من خلال ضبط المَعلمة origin في الطريقتَين
GetCredentialRequest() وCreatePublicKeyCredentialRequest() في Credential Manager. لمعالجة هذه الطلبات،
يستردّ موفّر بيانات الاعتماد origin باستخدام واجهة برمجة التطبيقات getOrigin().
لاسترداد origin، يحتاج تطبيق مزوّد بيانات الاعتماد إلى تمرير قائمة
بالمتصلين الموثوق بهم والمميّزين إلى
واجهة برمجة التطبيقات androidx.credentials.provider.CallingAppInfo's getOrigin(). يجب أن تكون هذه القائمة المسموح بها
عنصر JSON صالحًا. يتم عرض origin إذا كان packageName والملفات المرجعية للشهادة التي تم الحصول عليها من signingInfo تتطابق مع تلك الخاصة بتطبيق تم العثور عليه في privilegedAllowlist الذي تم تمريره إلى واجهة برمجة التطبيقات getOrigin(). بعد الحصول على قيمة origin، يجب أن يعتبر تطبيق الموفِّر هذا الطلب مميزًا ويضبط origin على بيانات العميل في AuthenticatorResponse، بدلاً من احتساب origin باستخدام توقيع تطبيق الاتصال.
إذا استرددت origin، استخدِم clientDataHash المقدَّم مباشرةً
في CreatePublicKeyCredentialRequest() أو
GetPublicKeyCredentialOption() بدلاً من تجميع clientDataJSON وتجزئته
أثناء طلب التوقيع. لتجنُّب مشاكل تحليل JSON، اضبط
قيمة عنصر نائب لـ clientDataJSON في استجابة المصادقة
والتأكيد. يستخدم "مدير كلمات المرور في Google" قائمة مسموح بها متاحة للجميع لطلبات البيانات إلى getOrigin(). بصفتك مقدّم بيانات اعتماد، يمكنك استخدام هذه القائمة أو تقديم قائمتك الخاصة بتنسيق JSON الموضَّح في واجهة برمجة التطبيقات. ويعود لمقدّم الخدمة اختيار القائمة التي سيتم استخدامها. للحصول على
إذن وصول مميّز من خلال موفّري بيانات الاعتماد التابعين لجهات خارجية، يُرجى الرجوع إلى
المستندات التي يوفّرها الطرف الثالث.
تفعيل مقدّمي الخدمات على جهاز
على المستخدمين تفعيل مقدّم الخدمة من خلال إعدادات الجهاز > كلمات المرور والحسابات > مقدّم الخدمة > تفعيل أو إيقاف.
fun createSettingsPendingIntent(): PendingIntent