1. مقدمة
ما هي واجهة برمجة تطبيقات FIDO2؟
تسمح واجهة برمجة تطبيقات FIDO2 لتطبيقات Android بإنشاء بيانات اعتماد قوية مستندة إلى مفتاح عام مُصدَّق عليها واستخدامها بغرض مصادقة المستخدمين. توفّر واجهة برمجة التطبيقات تنفيذ WebAuthn Client، الذي يتيح استخدام أدوات المصادقة القابلة للفهرسة (BLE) وNFC وUSB (مفاتيح الأمان) للتجوال، بالإضافة إلى برنامج مصادقة على النظام الأساسي يتيح للمستخدم إجراء المصادقة باستخدام بصمة الإصبع أو قفل الشاشة.
ما الذي ستنشئه...
في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيق Android مع وظيفة إعادة مصادقة بسيطة باستخدام أداة استشعار بصمة الإصبع. "إعادة المصادقة" هو عندما يسجِّل مستخدم الدخول إلى تطبيق، ثم يعيد المصادقة عند عودته إلى تطبيقك، أو عند محاولة الوصول إلى قسم مهم في تطبيقك. ويُشار إلى الحالة الثانية أيضًا باسم "المصادقة المرحة".
المعلومات التي ستطّلع عليها
ستتعلم كيفية طلب واجهة برمجة تطبيقات Android FIDO2 والخيارات التي يمكنك تقديمها لتلبية احتياجات مختلفة. ستتعرف أيضًا على أفضل الممارسات المتعلقة بإعادة المصادقة.
ما ستحتاجه...
- جهاز Android مزوّد بمستشعر بصمة الإصبع (يمكن أن يوفّر قفل الشاشة وظيفة مكافئة للتحقّق من هوية المستخدم، حتى في حال عدم توفُّر أداة استشعار بصمة الإصبع)
- الإصدار 7.0 من نظام التشغيل Android أو إصدار أحدث مع آخر التحديثات احرص على تسجيل بصمة الإصبع (أو قفل الشاشة).
2. بدء الإعداد
استنساخ المستودع
راجع مستودع جيت هب.
https://github.com/android/codelab-fido2
$ git clone https://github.com/android/codelab-fido2.git
ما الذي سننفّذه؟
- السماح للمستخدمين بتسجيل "أداة المصادقة على النظام الأساسي للتحقق من هوية المستخدم" (سيعمل هاتف Android الذي يحتوي على أداة استشعار بصمة الإصبع كجهاز واحد).
- يتم السماح للمستخدمين بإعادة المصادقة على أنفسهم للوصول إلى التطبيق باستخدام بصمة إصبعهم.
يمكنك معاينة ما تريد إنشاءه هنا.
بدء مشروع الدرس التطبيقي حول الترميز
ويرسل التطبيق المكتمل الطلبات إلى خادم على الرابط https://webauthn-codelab.glitch.me. يمكنك تجربة إصدار الويب من التطبيق نفسه هناك.
ستعمل على الإصدار الخاص بك من التطبيق.
- انتقِل إلى صفحة تعديل الموقع الإلكتروني على https://glitch.com/edit/#!/webauthn-codelab.
- البحث عن الخيار "إنشاء ريمكس للتعديل" في أعلى الجانب الأيسر. من خلال الضغط على الزر، يمكنك "شوكة" الرمز ومواصلة نسختك مع عنوان URL جديد للمشروع.
- انسخ اسم المشروع في أعلى يمين الصفحة (يمكنك تعديله كما تريد).
- الصقه في القسم
HOSTNAME
للملف.env
في هذا الخطأ.
3- ربط تطبيقك وموقع إلكتروني بروابط مواد العرض الرقمية
لاستخدام واجهة برمجة التطبيقات FIDO2 API على تطبيق Android، عليك ربطها بموقع إلكتروني ومشاركة بيانات الاعتماد بينها. ولإجراء ذلك، يمكنك الاستفادة من روابط مواد العرض الرقمية. يمكنك الإفصاح عن عمليات الربط من خلال استضافة ملف روابط التنقل إلى مواد العرض الرقمية بتنسيق JSON على موقعك الإلكتروني وإضافة رابط يؤدي إلى ملف "رابط مواد العرض الرقمية" إلى ملف بيان تطبيقك.
استضافة .well-known/assetlinks.json
في نطاقك
يمكنك تحديد عملية ربط بين تطبيقك والموقع الإلكتروني من خلال إنشاء ملف JSON ووضعه في .well-known/assetlinks.json
. لحسن الحظ، لدينا رمز خادم يعرض ملف assetlinks.json
تلقائيًا، وذلك من خلال إضافة مَعلمات البيئة التالية إلى ملف .env
بشكل خلل:
ANDROID_PACKAGENAME
: اسم حزمة تطبيقك (com.example.android.fido2)ANDROID_SHA256HASH
: تجزئة SHA256 لشهادة التوقيع
للحصول على تجزئة SHA256 لشهادة توقيع المطوّر، استخدِم الأمر أدناه. كلمة المرور التلقائية لملف تخزين المفاتيح لتصحيح الأخطاء هي "android".
$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
من خلال الوصول إلى https://<your-project-name>.glitch.me/.well-known/assetlinks.json
، من المفترض أن تظهر لك سلسلة JSON مثل هذه:
[{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://<your-project-name>.glitch.me"
}
}, {
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.example.android.fido2",
"sha256_cert_fingerprints": ["DE:AD:BE:EF:..."]
}
}]
فتح المشروع في "استوديو Android"
النقر على "فتح مشروع حالي على استوديو Android" على شاشة الترحيب في "استوديو Android"
اختر "android" مجلّد داخل المستودع.
ربط التطبيق بالريمكس
افتح ملف gradle.properties
. في أسفل الملف، غيِّر عنوان URL المضيف إلى ريمكس Glitch الذي أنشأته للتو.
// ...
# The URL of the server
host=https://<your-project-name>.glitch.me
في هذه المرحلة، يجب أن تكون إعدادات ميزة "روابط مواد العرض الرقمية" جاهزة للاستخدام.
4. التعرّف على طريقة عمل التطبيق الآن
لنبدأ بالتحقق من كيفية عمل التطبيق الآن. تأكَّد من اختيار app-start في مربّع التحرير والسرد "تشغيل الإعداد". انقر فوق "Run" (تشغيل) (مثلّث أخضر بجانب مربع التحرير والسرد) لتشغيل التطبيق على جهاز Android المرتبط.
عند تشغيل التطبيق، ستظهر لك شاشة لكتابة اسم المستخدم. دَهْ UsernameFragment
. لأغراض التوضيح، يقبل التطبيق والخادم أي اسم مستخدم. ما عليك سوى كتابة شيء والضغط على "التالي".
الشاشة التالية التي تراها هي AuthFragment
. ويمكن للمستخدم تسجيل الدخول من هنا باستخدام كلمة مرور. سنضيف لاحقًا ميزة لتسجيل الدخول باستخدام FIDO2 هنا. ومرة أخرى، وبغرض التوضيح، يقبل التطبيق والخادم أي كلمة مرور. ما عليك سوى كتابة شيء والضغط على "تسجيل الدخول".
هذه هي الشاشة الأخيرة في هذا التطبيق، "HomeFragment
". لا تظهر حاليًا سوى قائمة فارغة من بيانات الاعتماد هنا. الضغط على "إعادة المصادقة" يعيدك إلى AuthFragment
. الضغط على "تسجيل الخروج" يعيدك إلى UsernameFragment
. زر الإجراء الرئيسي مع علامة "+" لا تفعل أي شيء الآن، ولكنها ستبدأ تسجيل
بيانات اعتماد جديدة بعد تنفيذ مسار تسجيل FIDO2.
قبل البدء في الترميز، إليك أسلوب مفيد. في "استوديو Android"، اضغط على "قائمة المهام" في أسفل الصفحة سيعرض هذا القسم قائمة بجميع المهام في هذا الدرس التطبيقي حول الترميز. سنبدأ بأول TODO في القسم التالي.
5- تسجيل بيانات اعتماد باستخدام بصمة إصبع
لتفعيل المصادقة باستخدام بصمة الإصبع، يجب أولاً تسجيل بيانات الاعتماد التي تم إنشاؤها من قِبل مستخدم يُثبت صحة برنامج المصادقة على النظام الأساسي، وهو أداة مصادقة مضمّنة في الجهاز تتحقّق من المستخدم باستخدام المقاييس الحيوية، مثل أداة استشعار بصمة الإصبع.
كما رأينا في القسم السابق، لا يفعل زر الإجراء الرئيسي أي شيء الآن. لنرَ كيف يمكننا تسجيل بيانات اعتماد جديدة.
طلب واجهة برمجة تطبيقات الخادم: /auth/registerRequest
افتح AuthRepository.kt
وابحث عن TODO(1).
هنا، registerRequest
هي الطريقة التي يتم استدعاؤها عند الضغط على زر الإجراء الرئيسي (FAB). ونود أن نجعل هذه الطريقة تستدعي واجهة برمجة تطبيقات الخادم /auth/registerRequest
. تعرض واجهة برمجة التطبيقات رمز ApiResult
يتضمّن كل PublicKeyCredentialCreationOptions
الذي يحتاجه العميل لإنشاء بيانات اعتماد جديدة.
يمكننا بعد ذلك الاتصال بـ getRegisterPendingIntent
مع توفير الخيارات. تعرض واجهة برمجة تطبيقات FIDO2 هذه رمز PendingIntent لنظام التشغيل Android لفتح مربع حوار بصمة الإصبع وإنشاء بيانات اعتماد جديدة، ويمكننا إعادة رمز PendingIntent هذا إلى المتصل.
وعندئذٍ ستبدو الطريقة كما هو موضح أدناه.
suspend fun registerRequest(): PendingIntent? {
fido2ApiClient?.let { client ->
try {
val sessionId = dataStore.read(SESSION_ID)!!
when (val apiResult = api.registerRequest(sessionId)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
if (apiResult.sessionId != null) {
dataStore.edit { prefs ->
prefs[SESSION_ID] = apiResult.sessionId
}
}
val task = client.getRegisterPendingIntent(apiResult.data)
return task.await()
}
}
} catch (e: Exception) {
Log.e(TAG, "Cannot call registerRequest", e)
}
}
return null
}
فتح مربع حوار بصمة الإصبع للتسجيل
افتح HomeFragment.kt
وابحث عن TODO(2).
هذا هو المكان الذي تسترجع فيه واجهة المستخدم Intent من AuthRepository
. في هذه الحالة، سنستخدم عضو createCredentialIntentLauncher
لتفعيل رمز PendingIntent الذي حصلنا عليه كنتيجة للخطوة السابقة. سيؤدي هذا إلى فتح مربع حوار لإنشاء بيانات الاعتماد.
binding.add.setOnClickListener {
lifecycleScope.launch {
val intent = viewModel.registerRequest()
if (intent != null) {
createCredentialIntentLauncher.launch(
IntentSenderRequest.Builder(intent).build()
)
}
}
}
تلقّي ActivityResult مع بيانات الاعتماد الجديدة
افتح HomeFragment.kt
وابحث عن TODO(3).
ويتم استدعاء طريقة handleCreateCredentialResult
هذه بعد إغلاق مربّع حوار بصمة الإصبع. إذا تم إنشاء بيانات اعتماد بنجاح، سيحتوي عضو data
في ActivityResult على معلومات بيانات الاعتماد.
أولاً، علينا استخراج PublicKeyCredential من data
. يحتوي Intent للبيانات على حقل إضافي لمصفوفة بايت مع المفتاح Fido.FIDO2_KEY_CREDENTIAL_EXTRA
. يمكنك استخدام طريقة ثابتة في PublicKeyCredential
تُسمّى deserializeFromBytes
لتحويل مصفوفة البايت إلى كائن PublicKeyCredential
.
تحقَّق بعد ذلك مما إذا كان عضو response
لكائن بيانات الاعتماد هذا AuthenticationErrorResponse
. إذا كانت كذلك، هذا يعني أنّه حدث خطأ أثناء إنشاء بيانات الاعتماد. وإلا فيمكننا إرسال بيانات الاعتماد إلى الواجهة الخلفية لدينا.
ستبدو الطريقة النهائية على النحو التالي:
private fun handleCreateCredentialResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_LONG).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_LONG)
.show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_LONG)
.show()
} else {
viewModel.registerResponse(credential)
}
}
}
}
طلب واجهة برمجة تطبيقات الخادم: /auth/registerResponse
افتح AuthRepository.kt
وابحث عن TODO(4).
يتم استدعاء طريقة registerResponse
هذه بعد أن تنشئ واجهة المستخدم بيانات اعتماد جديدة بنجاح، ونريد إعادة إرسالها إلى الخادم.
يحتوي الكائن PublicKeyCredential
على معلومات عن بيانات الاعتماد التي تم إنشاؤها حديثًا بداخله. نريد الآن أن نتذكر مفتاحنا المحلي كي يتسنى لنا تمييزه عن المفاتيح الأخرى المسجلة على الخادم. في الكائن PublicKeyCredential
، خذ السمة rawId
الخاصة به ووضعها في متغيّر سلسلة محلية باستخدام toBase64
.
نحن الآن جاهزون لإرسال المعلومات إلى الخادم. استخدِم api.registerResponse
لطلب واجهة برمجة تطبيقات الخادم وإرسال الردّ. تحتوي القيمة المعروضة على قائمة بجميع بيانات الاعتماد المسجّلة على الخادم، بما في ذلك بيانات الاعتماد الجديدة.
وأخيرًا، يمكننا حفظ النتائج في DataStore
. يجب حفظ قائمة بيانات الاعتماد بالمفتاح CREDENTIALS
كملف StringSet
. يمكنك استخدام toStringSet
لتحويل قائمة بيانات الاعتماد إلى StringSet
.
بالإضافة إلى ذلك، نحفظ رقم تعريف بيانات الاعتماد مع المفتاح LOCAL_CREDENTIAL_ID
.
suspend fun registerResponse(credential: PublicKeyCredential) {
try {
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = credential.rawId.toBase64()
when (val result = api.registerResponse(sessionId, credential)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
dataStore.edit { prefs ->
result.sessionId?.let { prefs[SESSION_ID] = it }
prefs[CREDENTIALS] = result.data.toStringSet()
prefs[LOCAL_CREDENTIAL_ID] = credentialId
}
}
}
} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
}
}
قم بتشغيل التطبيق، وستتمكن من النقر فوق FAB وتسجيل بيانات اعتماد جديدة.
6- مصادقة المستخدم باستخدام بصمة الإصبع
لدينا الآن بيانات اعتماد مسجَّلة في التطبيق والخادم. يمكننا الآن استخدامه للسماح للمستخدم بتسجيل الدخول. نحن بصدد إضافة ميزة تسجيل الدخول ببصمة الإصبع إلى "AuthFragment
". عندما يصل المستخدم إليها، يظهر مربّع حوار بصمة الإصبع. عندما تنجح المصادقة، تتم إعادة توجيه المستخدم إلى HomeFragment
.
طلب واجهة برمجة تطبيقات الخادم: /auth/signinRequest
افتح AuthRepository.kt
وابحث عن TODO(5).
يتم استدعاء طريقة signinRequest
هذه عند فتح AuthFragment
. هنا، نريد طلب الخادم ومعرفة ما إذا كان يمكننا السماح للمستخدم بتسجيل الدخول باستخدام FIDO2.
أولاً، علينا استرداد PublicKeyCredentialRequestOptions
من الخادم. استخدِم api.signInRequest
لطلب واجهة برمجة تطبيقات الخادم. تحتوي قيمة ApiResult
المعروضة على PublicKeyCredentialRequestOptions
.
من خلال PublicKeyCredentialRequestOptions
، يمكننا استخدام FIDO2 API getSignIntent
لإنشاء رمز PendingIntent لفتح مربّع حوار الملف المرجعي.
وأخيرًا، يمكننا إرجاع كائن PendingIntent إلى واجهة المستخدم.
suspend fun signinRequest(): PendingIntent? {
fido2ApiClient?.let { client ->
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)
if (credentialId != null) {
when (val apiResult = api.signinRequest(sessionId, credentialId)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
val task = client.getSignPendingIntent(apiResult.data)
return task.await()
}
}
}
}
return null
}
فتح مربع حوار بصمات الإصبع للتأكيد
افتح AuthFragment.kt
وابحث عن TODO(6).
وهذا الأمر يشبه إلى حد كبير ما فعلناه في عملية التسجيل. يمكننا فتح مربّع حوار بصمة الإصبع مع عضو signIntentLauncher
.
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
launch {
viewModel.signinRequests.collect { intent ->
signIntentLauncher.launch(
IntentSenderRequest.Builder(intent).build()
)
}
}
launch {
...
}
}
التعامل مع ActivityResult
افتح AuthFragment.kt وابحث عن TODO(7).
مرة أخرى، هذه هي الطريقة نفسها التي اتّبعناها في عملية التسجيل. يمكننا استخراج PublicKeyCredential
والتحقق من وجود خطأ وتمريره إلى ViewModel.
private fun handleSignResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT)
.show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
.show()
} else {
viewModel.signinResponse(credential)
}
}
}
}
طلب واجهة برمجة تطبيقات الخادم: /auth/signinResponse
افتح AuthRepository.kt
وابحث عن TODO(8).
يحتوي الكائن PublicKeyCredential على رقم تعريف بيانات اعتماد بالتنسيق keyHandle
. تمامًا كما فعلنا في تدفق التسجيل، دعنا نحفظ هذا في متغير سلسلة محلية حتى نتمكن من تخزينه لاحقًا.
نحن الآن جاهزون لطلب واجهة برمجة تطبيقات الخادم باستخدام api.signinResponse
. تحتوي القيمة المعروضة على قائمة ببيانات الاعتماد.
في هذه المرحلة، تم تسجيل الدخول بنجاح. علينا تخزين جميع النتائج في DataStore
. يجب تخزين قائمة بيانات الاعتماد على أنّها StringSet باستخدام المفتاح CREDENTIALS
. يجب أن يتم تخزين رقم تعريف بيانات الاعتماد المحلي الذي حفظناه أعلاه كسلسلة تحتوي على المفتاح LOCAL_CREDENTIAL_ID
.
وأخيرًا، نحتاج إلى تعديل حالة تسجيل الدخول لتتمكّن واجهة المستخدم من إعادة توجيه المستخدم إلى HomeFragment. ويمكن إجراء ذلك من خلال إرسال كائن SignInState.SignedIn
إلى SharedFlow المُسمّى signInStateMutable
. نريد أيضًا استدعاء refreshCredentials
لجلب بيانات اعتماد المستخدم حتى يتم إدراجها في واجهة المستخدم.
suspend fun signinResponse(credential: PublicKeyCredential) {
try {
val username = dataStore.read(USERNAME)!!
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = credential.rawId.toBase64()
when (val result = api.signinResponse(sessionId, credential)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
dataStore.edit { prefs ->
result.sessionId?.let { prefs[SESSION_ID] = it }
prefs[CREDENTIALS] = result.data.toStringSet()
prefs[LOCAL_CREDENTIAL_ID] = credentialId
}
signInStateMutable.emit(SignInState.SignedIn(username))
refreshCredentials()
}
}
} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
}
}
شغِّل التطبيق وانقر على "إعادة المصادقة" لفتح AuthFragment
. من المفترض أن يظهر لك الآن مربع حوار بصمة إصبع يطلب منك تسجيل الدخول باستخدام بصمة إصبعك.
تهانينا! لقد تعرّفت الآن على كيفية استخدام FIDO2 API على Android للتسجيل وتسجيل الدخول.
7. تهانينا!
لقد أكملت الدرس التطبيقي حول الترميز بنجاح، وهو أول واجهة برمجة تطبيقات Android FIDO2 API.
ما تعلمته
- كيفية تسجيل بيانات الاعتماد باستخدام مستخدم يتحقّق من مصادقة النظام الأساسي
- كيفية مصادقة مستخدم باستخدام برنامج مصادقة مسجَّل
- الخيارات المتاحة لتسجيل برنامج مصادقة جديد.
- أفضل ممارسات تجربة المستخدم لإعادة المصادقة باستخدام أداة استشعار المقاييس الحيوية.
الخطوة التالية
- تعرّف على كيفية إنشاء تجربة مماثلة في موقع ويب.
يمكنك التعرّف على ذلك من خلال تجربة الدرس التطبيقي حول الترميز WebAuthn الخاص بك.
الموارد
نشكر يوري أكيرمان من تحالف FIDO Alliance على مساعدتك.