1. قبل البدء
تطرح حلول المصادقة التقليدية عددًا من تحديات الأمان وسهولة الاستخدام.
يتم استخدام كلمات المرور على نطاق واسع، ولكن...
- من السهل نسيانها
- يحتاج المستخدمون إلى المعرفة لإنشاء كلمات مرور قوية.
- من السهل على المهاجمين خداع المستخدمين وجمع بياناتهم وإعادة تشغيلها.
عمل فريق Android على إنشاء واجهة برمجة التطبيقات Credential Manager API لتبسيط تجربة تسجيل الدخول ومعالجة مخاطر الأمان من خلال إتاحة مفاتيح المرور، وهي الجيل التالي من معايير المجال لـ المصادقة بدون كلمة مرور.
يجمع تطبيق "مدير بيانات الاعتماد" بين إمكانية استخدام مفاتيح المرور وطرق المصادقة التقليدية، مثل كلمات المرور وميزة "تسجيل الدخول باستخدام حساب Google" وما إلى ذلك.
سيتمكّن المستخدمون من إنشاء مفاتيح مرور وتخزينها في "مدير كلمات المرور في Google"، ما يؤدي إلى مزامنة مفاتيح المرور هذه على جميع أجهزة Android التي سجّل المستخدم الدخول إليها. يجب إنشاء مفتاح مرور وربطه بحساب مستخدم وتخزين المفتاح العام له على خادم قبل أن يتمكّن المستخدم من تسجيل الدخول باستخدامه.
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية الاشتراك باستخدام مفاتيح المرور وكلمة المرور باستخدام Credential Manager API واستخدامها لأغراض المصادقة في المستقبل. هناك عمليتان تتضمّنان ما يلي:
- الاشتراك : باستخدام مفاتيح المرور وكلمة المرور
- تسجيل الدخول : باستخدام مفاتيح المرور وكلمة المرور المحفوظة
المتطلبات الأساسية
- فهم أساسي لكيفية تشغيل التطبيقات في "استوديو Android"
- فهم أساسي لمسار المصادقة في تطبيقات Android
- فهم أساسي لمفاتيح المرور
المُعطيات
- كيفية إنشاء مفتاح مرور
- كيفية حفظ كلمة المرور في مدير كلمات المرور
- كيفية مصادقة المستخدمين باستخدام مفتاح مرور أو كلمة مرور محفوظة
المتطلبات
إحدى مجموعات الأجهزة التالية:
- جهاز Android يعمل بالإصدار 9 أو إصدار أحدث (للمفاتيح المميّزة) والإصدار 4.4 أو إصدار أحدث(لمصادقة كلمة المرور من خلال واجهة برمجة التطبيقات Credential Manager API)
- الجهاز المزوّد بجهاز استشعار حيوي
- احرص على تسجيل مقياس حيوي (أو قفل شاشة).
- إصدار المكوِّن الإضافي Kotlin : 1.8.10
2. الإعداد
- يمكنك استنساخ هذا المستودع على الكمبيوتر المحمول من فرع credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
- انتقِل إلى وحدة CredentialManager وافتح المشروع في Android Studio.
لنطّلِع على الحالة الأولية للتطبيق
لمعرفة كيفية عمل الحالة الأولية للتطبيق، اتّبِع الخطوات التالية:
- افتح التطبيق.
- ستظهر لك شاشة رئيسية تتضمّن زرَّي الاشتراك وتسجيل الدخول. لا تؤدي هذه الأزرار أيّ وظيفة إلى الآن، ولكن سنفعّل وظائفها في الأقسام القادمة.
3- إضافة إمكانية الاشتراك باستخدام مفاتيح المرور
عند الاشتراك في حساب جديد على تطبيق Android يستخدم واجهة برمجة التطبيقات Credential Manager API، يمكن للمستخدمين إنشاء مفتاح مرور لحساباتهم. سيتم تخزين مفتاح المرور هذا بأمان في مقدّم بيانات الاعتماد الذي يختاره المستخدم، وسيتم استخدامه لعمليات تسجيل الدخول المستقبلية، بدون أن يُطلب من المستخدم إدخال كلمة المرور في كل مرة.
الآن، عليك إنشاء مفتاح مرور وتسجيل بيانات اعتماد المستخدم باستخدام المقاييس الحيوية أو قفل الشاشة.
الاشتراك باستخدام مفتاح مرور
يحدِّد الرمز البرمجي داخل Credential Manager/app/main/java/SignUpFragment.kt حقل نصي "اسم المستخدم" وزرًا للاشتراك باستخدام مفتاح مرور.
تمرير التحدي واستجابة json الأخرى إلى طلب createPasskey()
قبل إنشاء مفتاح مرور، عليك طلب المعلومات اللازمة من الخادم لنقلها إلى واجهة برمجة التطبيقات Credential Manager API أثناء طلب createCredential().
لديك حاليًا استجابة وهمية في مواد عرض مشروعك، تُسمى RegFromServer.txt، والتي تعرض المَعلمات اللازمة في هذا الدليل التعليمي حول الرموز البرمجية.
- في تطبيقك، انتقِل إلى SignUpFragment.kt، وابحث عن الطريقة signUpWithPasskeys التي ستكتب فيها منطق إنشاء مفتاح مرور والسماح للمستخدم بالدخول. يمكنك العثور على الطريقة في الصف نفسه.
- تحقّق من مجموعة else باستخدام تعليق لاستدعاء
createPasskey()
واستبدِلها بالرمز البرمجي التالي:
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
سيتم استدعاء هذه الطريقة بعد ملء اسم مستخدم صالح على الشاشة.
- داخل طريقة
createPasskey()
، عليك إنشاءCreatePublicKeyCredentialRequest()
مع عرض المَعلمات اللازمة.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
تقرأ طريقة fetchRegistrationJsonFromServer()
استجابة JSON PublicKeyCredentialCreationOptions
لخادم محاكي من مواد العرض وتُعيد ملف JSON للتسجيل ليتم تمريره أثناء إنشاء مفتاح المرور.
- ابحث عن الطريقة
fetchRegistrationJsonFromServer()
واستبدِل TODO بالرمز التالي لعرض JSON وأزِل أيضًا بيان عرض السلسلة الفارغة :
SignUpFragment.kt
//TODO fetch registration mock response
val response = requireContext().readFromAsset("RegFromServer")
//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
.replace("<userName>", binding.username.text.toString())
.replace("<userDisplayName>", binding.username.text.toString())
.replace("<challenge>", getEncodedChallenge())
- ملف JSON هذا غير مكتمل ويحتوي على 4 حقول يجب استبدالها.
- يجب أن يكون UserId فريدًا حتى يتمكّن المستخدم من إنشاء مفاتيح مرور متعددة (إذا لزم الأمر). استبدِل
<userId>
بالقيمةuserId
التي تم إنشاؤها. - يجب أن يكون
<challenge>
فريدًا أيضًا حتى تتمكّن من إنشاء تحدّي فريد عشوائي. الطريقة متوفّرة في الرمز البرمجي.
قد يعرض ردّ الخادم الفعلي PublicKeyCredentialCreationOptions
المزيد من الخيارات. في ما يلي مثال على بعض هذه الحقول:
{
"challenge": String,
"rp": {
"name": String,
"id": String
},
"user": {
"id": String,
"name": String,
"displayName": String
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
يوضّح الجدول التالي بعض المَعلمات المهمة في عنصر PublicKeyCredentialCreationOptions
:
المعلّمات | الأوصاف |
سلسلة عشوائية ينشئها الخادم تحتوي على قدر كافٍ من التشويش يجعل تخمينها غير ممكن. يجب ألا يقلّ طولها عن 16 بايت. هذا الحقل مطلوب، ولكن لا يتم استخدامه أثناء التسجيل ما لم يتم إجراء إقرار. | |
المعرّف الفريد للمستخدم يجب ألا تتضمّن هذه القيمة معلومات تحدّد الهوية الشخصية، مثل عناوين البريد الإلكتروني أو أسماء المستخدمين. وستعمل بشكل جيد قيمة عشوائية تبلغ 16 بايت يتم إنشاؤها لكل حساب. | |
يجب أن يحتوي هذا الحقل على معرّف فريد للحساب يمكن للمستخدم التعرّف عليه، مثل عنوان بريده الإلكتروني أو اسمه. سيتم عرض هذا في أداة اختيار الحسابات. (في حال استخدام اسم مستخدم، استخدِم القيمة نفسها المستخدَمة في مصادقة كلمة المرور). | |
هذا الحقل هو اسم اختياري وأكثر سهولة للاستخدام للحساب. | |
يتوافق كيان الطرف المعنيّ بالاعتماد مع تفاصيل طلبك. وتتضمّن السمات التالية:
| |
قائمة بالخوارزميات المسموح بها وأنواع المفاتيح يجب أن تحتوي هذه القائمة على عنصر واحد على الأقل. | |
قد يكون المستخدم الذي يحاول تسجيل جهاز قد سجّل أجهزة أخرى. للحد من إنشاء بيانات اعتماد متعددة للحساب نفسه على معرّف مصادقة واحد، يمكنك بعد ذلك تجاهل هذه الأجهزة. يجب أن يحتوي العنصر | |
يشير إلى ما إذا كان يجب إرفاق الجهاز بالنظام الأساسي أو لا أو ما إذا لم يكن هناك شرط لذلك. اضبط هذه القيمة على | |
| حدِّد القيمة |
إنشاء بيانات اعتماد
- بعد إنشاء
CreatePublicKeyCredentialRequest()
، عليك الاتصال برقمcreateCredential()
وإرسال الطلب الذي تم إنشاؤه.
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- تُرسل المعلومات المطلوبة إلى
createCredential()
. - بعد نجاح الطلب، ستظهر لك شاشة سفلية على الشاشة تطلب منك إنشاء مفتاح مرور.
- يمكن للمستخدمين الآن إثبات هويتهم من خلال المقاييس الحيوية أو قفل الشاشة وما إلى ذلك.
- يمكنك التعامل مع مستوى ظهور العروض المعروضة والتعامل مع الاستثناءات في حال تعذّر الطلب أو عدم نجاحه لأي سبب. يتم تسجيل رسائل الخطأ هنا وعرضها على التطبيق في مربّع حوار الخطأ. يمكنك الاطّلاع على سجلّات الأخطاء الكاملة من خلال Android Studio أو باستخدام الأمر
adb debug
.
- أخيرًا، عليك إكمال عملية التسجيل. يُرسِل التطبيق بيانات اعتماد المفتاح العام إلى الخادم الذي يسجِّلها للمستخدم الحالي.
لقد استخدمنا هنا خادمًا وهميًا، لذا نعرض فقط القيمة true للإشارة إلى أنّ الخادم قد حفظ المفتاح العام المسجَّل لأغراض المصادقة والتحقّق في المستقبل. يمكنك الاطّلاع على مزيد من المعلومات حول تسجيل مفاتيح المرور من جهة الخادم لتنفيذ ذلك بنفسك.
داخل طريقة signUpWithPasskeys()
، ابحث عن التعليق ذي الصلة واستبدِله بالرمز البرمجي التالي:
SignUpFragment.kt
//TODO : complete the registration process after sending public key credential to your server and let the user in
data?.let {
registerResponse()
DataProvider.setSignedInThroughPasskeys(true)
listener.showHome()
}
- يعرض
registerResponse()
القيمةtrue
، ما يشير إلى أنّ الخادم النموذجي قد حفظ المفتاح العام لاستخدامه في المستقبل. - اضبط علامة
setSignedInThroughPasskeys
علىtrue
. - بعد تسجيل الدخول، يمكنك إعادة توجيه المستخدم إلى الشاشة الرئيسية.
قد يحتوي PublicKeyCredential
الحقيقي على المزيد من الحقول. في ما يلي مثال على هذه الحقول:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
يوضّح الجدول التالي بعض المَعلمات المهمة في عنصر PublicKeyCredential
:
المعلّمات | الأوصاف |
معرّف مُشفَّر بترميز Base64URL لمفتاح المرور الذي تم إنشاؤه يساعد رقم التعريف هذا المتصفّح في تحديد ما إذا كان هناك مفتاح مرور مطابق في الجهاز عند المصادقة. يجب تخزين هذه القيمة في قاعدة البيانات في الخلفية. | |
نسخة من | |
بيانات العميل المشفّرة في عنصر | |
عنصر شهادة مُشفَّر بتنسيق |
شغِّل التطبيق، وسيكون بإمكانك النقر على الزر الاشتراك باستخدام مفاتيح المرور وإنشاء مفتاح مرور.
4. حفظ كلمة مرور في "موفِّر بيانات الاعتماد"
في هذا التطبيق، داخل شاشة "الاشتراك"، سبق أن تم تنفيذ ميزة الاشتراك باستخدام اسم المستخدم وكلمة المرور لأغراض توضيحية.
لحفظ بيانات اعتماد كلمة مرور المستخدم مع مقدّم كلمة المرور، عليك تنفيذ CreatePasswordRequest
لنقلها إلى createCredential()
لحفظ كلمة المرور.
- ابحث عن طريقة
signUpWithPassword()
، واستبدِل TODO باستدعاءcreatePassword
:
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- داخل الطريقة
createPassword()
، عليك إنشاء طلب كلمة مرور على النحو التالي، واستبدال TODO بالرمز البرمجي التالي:
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- بعد ذلك، ضمن طريقة
createPassword()
، أنشئ بيانات اعتماد باستخدام طلب إنشاء كلمة مرور واحفظ بيانات اعتماد كلمة مرور المستخدم مع موفّر كلمة المرور. استبدِل TODO بالرمز التالي:
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(requireActivity(), request) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- لقد نجحت الآن في حفظ بيانات اعتماد كلمة المرور مع مقدّم كلمة مرور المستخدم للمصادقة باستخدام كلمة مرور بنقرة واحدة فقط.
5- إضافة إمكانية المصادقة باستخدام مفتاح مرور أو كلمة مرور
يمكنك الآن استخدامها كطريقة للمصادقة على تطبيقك بأمان.
الحصول على التحدي والخيارات الأخرى لتمرير طلب getPasskey()
قبل أن تطلب من المستخدم المصادقة، عليك طلب مَعلمات لتمرير WebAuthn JSON من الخادم، بما في ذلك طلب التحقّق.
لديك ردّ وهمي في مواد العرض (AuthFromServer.txt) يعرض هذه المَعلمات في هذا الدليل التعليمي.
- في تطبيقك، انتقِل إلى SignInFragment.kt، وابحث عن الطريقة
signInWithSavedCredentials
التي ستكتب فيها منطق المصادقة من خلال مفتاح المرور أو كلمة المرور المحفوظة، ثم اسمح للمستخدم بالدخول: - تحقّق من مجموعة else باستخدام تعليق لاستدعاء
createPasskey()
واستبدِلها بالرمز البرمجي التالي:
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- داخل الطريقة getSavedCredentials()، عليك إنشاء
GetPublicKeyCredentialOption()
بالمَعلمات اللازمة للحصول على بيانات الاعتماد من مقدّم بيانات الاعتماد.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
تقرأ طريقة fetchAuthJsonFromServer()
استجابة JSON للمصادقة من مواد العرض وتُعيد JSON للمصادقة لاسترداد جميع مفاتيح المرور المرتبطة بحساب المستخدم هذا.
المَعلمة الثانية لدالة GetPublicKeyCredentialOption() هي clientDataHash
، وهي تشير إلى تجزئة تُستخدَم للتحقّق من هوية الطرف الموثوق به. لا تضبط هذا الخيار إلّا إذا كنت قد ضبطت GetCredentialRequest.origin
. بالنسبة إلى نموذج التطبيق، يتم ضبط هذا الخيار على null
.
- ابحث عن الطريقة fetchAuthJsonFromServer() واستبدِل TODO بالرمز البرمجي التالي لعرض json وأزِل أيضًا بيان عرض السلسلة الفارغة:
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
ملاحظة : تم تصميم خادم هذا الدليل التعليمي لعرض ملف JSON مشابه قدر الإمكان لقائمة PublicKeyCredentialRequestOptions
التي يتم تمريرها إلى طلب getCredential() في واجهة برمجة التطبيقات. يتضمّن مقتطف الرمز البرمجي التالي بعض الأمثلة على الخيارات التي قد تتلقّاها في ردّ حقيقي:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
يوضّح الجدول التالي بعض المَعلمات المهمة في عنصر PublicKeyCredentialRequestOptions
:
المعلّمات | الأوصاف |
تحدٍّ ينشئه الخادم في عنصر | |
رقم تعريف مقدّم الخدمة هو نطاق. يمكن للموقع الإلكتروني تحديد نطاقه أو لاحقة قابلة للتسجيل. يجب أن تتطابق هذه القيمة مع المَعلمة |
- بعد ذلك، عليك إنشاء عنصر
PasswordOption()
لاسترداد جميع كلمات المرور المحفوظة في موفِّر كلمات المرور من خلال واجهة برمجة التطبيقات Credential Manager API لحساب المستخدم هذا. داخل طريقةgetSavedCredentials()
، ابحث عن TODO واستبدِله بما يلي:
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
الحصول على بيانات الاعتماد
- بعد ذلك، عليك الاتصال بفريق
getCredential()
لطلب جميع الخيارات أعلاه لاسترداد بيانات الاعتماد المرتبطة:
SignInFragment.kt
//TODO call getCredential() with required credential options
val result = try {
credentialManager.getCredential(
requireActivity(),
GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
} catch (e: Exception) {
configureViews(View.INVISIBLE, true)
Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
activity?.showErrorAlert(
"An error occurred while authenticating through saved credentials. Check logs for additional details"
)
return null
}
if (result.credential is PublicKeyCredential) {
val cred = result.credential as PublicKeyCredential
DataProvider.setSignedInThroughPasskeys(true)
return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
val cred = result.credential as PasswordCredential
DataProvider.setSignedInThroughPasskeys(false)
return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
//If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}
- تُرسل المعلومات المطلوبة إلى
getCredential()
. يأخذ هذا الإجراء قائمة خيارات بيانات الاعتماد وسياق النشاط لعرض الخيارات في اللوحة السفلية في هذا السياق. - بعد نجاح الطلب، ستظهر لك لوحة سفلية على الشاشة تعرض جميع بيانات الاعتماد التي تم إنشاؤها للحساب المرتبط.
- يمكن للمستخدمين الآن إثبات هويتهم من خلال المقاييس الحيوية أو قفل الشاشة وما إلى ذلك لمصادقة بيانات الاعتماد التي تم اختيارها.
- إذا كانت بيانات الاعتماد التي تم اختيارها هي
PublicKeyCredential
، اضبط العلامةsetSignedInThroughPasskeys
علىtrue
. ويمكنك بدلاً من ذلك ضبطها علىfalse
.
يتضمّن مقتطف الرمز البرمجي التالي مثالاً على عنصر PublicKeyCredential
:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
الجدول التالي ليس شاملاً، ولكنه يحتوي على المَعلمات المهمة في عنصر PublicKeyCredential
:
المعلّمات | الأوصاف |
رقم التعريف المشفَّر بترميز Base64URL لمستند اعتماد مفتاح المرور الذي تم إثبات صحته | |
نسخة من | |
عنصر | |
عنصر | |
عنصر | |
عنصر |
- أخيرًا، عليك إكمال عملية المصادقة. عادةً ما يُرسِل التطبيق بعد إكمال المستخدم لمصادقة مفتاح المرور بيانات اعتماد المفتاح العام التي تحتوي على بيان مصادقة إلى الخادم الذي يُثبت صحة البيان ويُجري مصادقة المستخدم.
لقد استخدمنا هنا خادمًا وهميًا، لذا سنعرض القيمة true
للإشارة إلى أنّ الخادم قد تحقّق من العبارة. يمكنك الاطّلاع على مزيد من المعلومات حول مصادقة مفتاح المرور من جهة الخادم لتنفيذها بنفسك.
داخل الطريقة signInWithSavedCredentials()
، ابحث عن التعليق ذي الصلة واستبدِله بالرمز البرمجي التالي:
SignInFragment.kt
//TODO : complete the authentication process after validating the public key credential to your server and let the user in.
data?.let {
sendSignInResponseToServer()
listener.showHome()
}
- يعرض
sendSigninResponseToServer()
القيمة true (صحيح) للإشارة إلى أنّ الخادم (المزيف) قد تحقّق من المفتاح العام لاستخدامه في المستقبل. - بعد تسجيل الدخول، يمكنك إعادة توجيه المستخدم إلى الشاشة الرئيسية.
شغِّل التطبيق وانتقِل إلى تسجيل الدخول > تسجيل الدخول باستخدام مفاتيح المرور/كلمة المرور المحفوظة، وجرِّب تسجيل الدخول باستخدام بيانات الاعتماد المحفوظة.
تجربة الميزة
نفَّذت عملية إنشاء مفاتيح المرور وحفظ كلمة المرور في "مدير بيانات الاعتماد" والمصادقة من خلال مفاتيح المرور أو كلمة المرور المحفوظة باستخدام واجهة برمجة التطبيقات Credential Manager API في تطبيق Android.
6- تهانينا!
لقد أكملت هذا الدرس التطبيقي حول الترميز. إذا أردت الاطّلاع على الحل النهائي، يمكنك الانتقال إلى https://github.com/android/identity-samples/tree/main/CredentialManager.
إذا كانت لديك أي أسئلة، يُرجى طرحها على StackOverflow باستخدام علامة passkey
.