1. مقدمه
FIDO2 API چیست؟
FIDO2 API به برنامههای Android اجازه میدهد تا اعتبارنامههای قوی مبتنی بر کلید عمومی تأیید شده را برای احراز هویت کاربران ایجاد و استفاده کنند. API یک پیادهسازی WebAuthn Client را ارائه میکند که از استفاده از احراز هویت رومینگ BLE، NFC و USB (کلیدهای امنیتی) و همچنین یک تأییدکننده پلتفرم پشتیبانی میکند که به کاربر اجازه میدهد با استفاده از اثر انگشت یا قفل صفحه خود احراز هویت کند.
چیزی که خواهی ساخت...
در این کد لبه، شما قصد دارید یک اپلیکیشن اندرویدی با قابلیت احراز هویت مجدد ساده با استفاده از حسگر اثر انگشت بسازید. «تأیید هویت مجدد» زمانی است که کاربر به برنامهای وارد میشود، سپس وقتی به برنامه شما برمیگردد یا زمانی که سعی میکند به بخش مهمی از برنامه شما دسترسی پیدا کند ، دوباره احراز هویت میشود . مورد دوم نیز به عنوان "تأیید هویت مرحله به مرحله" نامیده می شود.
آنچه یاد خواهید گرفت...
نحوه فراخوانی Android FIDO2 API و گزینه هایی را که می توانید برای مناسبت های مختلف ارائه دهید، یاد خواهید گرفت. همچنین بهترین شیوه های خاص را مجدداً تأیید خواهید کرد.
آنچه شما نیاز خواهید داشت ...
- دستگاه اندرویدی با حسگر اثر انگشت (حتی بدون حسگر اثر انگشت، قفل صفحه میتواند عملکردی معادل برای تأیید کاربر ارائه دهد)
- سیستم عامل اندروید 7.0 یا بالاتر با آخرین به روز رسانی ها. حتما اثر انگشت (یا قفل صفحه) را ثبت کنید.
2. راه اندازی
Repository را شبیه سازی کنید
مخزن GitHub را بررسی کنید.
https://github.com/android/codelab-fido2
$ git clone https://github.com/android/codelab-fido2.git
قرار است چه چیزی را اجرا کنیم؟
- به کاربران اجازه دهید «تأیید کننده اعتبار پلتفرم تأییدکننده کاربر» را ثبت کنند (تلفن اندرویدی با حسگر اثر انگشت خود به عنوان یکی عمل می کند).
- به کاربران اجازه دهید با استفاده از اثر انگشت خود را مجدداً در برنامه تأیید کنند.
از اینجا می توانید پیش نمایش آنچه را که می خواهید بسازید مشاهده کنید.
پروژه Codelab خود را شروع کنید
برنامه تکمیل شده درخواست ها را به سروری در https://webauthn-codelab.glitch.me ارسال می کند. می توانید نسخه وب همان برنامه را در آنجا امتحان کنید.
شما می خواهید روی نسخه خود برنامه کار کنید.
- به صفحه ویرایش وب سایت در https://glitch.com/edit/#!/webauthn-codelab بروید.
- دکمه «ریمیکس برای ویرایش» را در گوشه بالا سمت راست پیدا کنید. با فشردن دکمه، میتوانید کد را «فشار» کنید و با نسخه شخصی خود همراه با URL پروژه جدید ادامه دهید.
- نام پروژه را در بالا سمت چپ کپی کنید (می توانید آن را به دلخواه تغییر دهید).
- آن را در بخش
HOSTNAME
فایل.env
در اشکال قرار دهید.
3. برنامه و یک وب سایت خود را با پیوندهای دارایی دیجیتال مرتبط کنید
برای استفاده از FIDO2 API در یک برنامه اندروید، آن را با یک وب سایت مرتبط کنید و اعتبارنامه ها را بین آنها به اشتراک بگذارید. برای انجام این کار، از پیوندهای دارایی دیجیتال استفاده کنید. میتوانید با میزبانی فایل JSON پیوندهای دارایی دیجیتال در وبسایت خود، و افزودن پیوندی به فایل پیوند دارایی دیجیتال به مانیفست برنامه خود، ارتباط خود را اعلام کنید.
.well-known/assetlinks.json
در دامنه خود میزبانی کنید
می توانید با ایجاد یک فایل JSON و قرار دادن آن در .well-known/assetlinks.json
، ارتباطی بین برنامه خود و وب سایت تعریف کنید. خوشبختانه، ما یک کد سرور داریم که فایل assetlinks.json
را بهطور خودکار نمایش میدهد، فقط با افزودن پارامترهای محیط زیر به فایل .env
در نقص:
-
ANDROID_PACKAGENAME
: نام بسته برنامه شما (com.example.android.fido2) -
ANDROID_SHA256HASH
: SHA256 گواهی امضای شما را هش کنید
برای دریافت هش SHA256 گواهی امضای توسعه دهنده خود، از دستور زیر استفاده کنید. رمز عبور پیشفرض فروشگاه کلید دیباگ «اندروید» است.
$ 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 Studio» کلیک کنید.
پوشه "اندروید" را در داخل مخزن بررسی کنید.
برنامه را با ریمیکس خود مرتبط کنید
فایل 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
است. در حال حاضر، فقط یک لیست خالی از اعتبارنامه ها را در اینجا می بینید. با فشار دادن "Reauth" به AuthFragment
برمی گردید. با فشار دادن "Sign Out" به UsernameFragment
برمی گردید. دکمه اکشن شناور با علامت "+" اکنون کاری انجام نمی دهد، اما ثبت a را آغاز می کند
پس از اجرای جریان ثبت نام FIDO2، اعتبار جدید
قبل از شروع کدنویسی، در اینجا یک تکنیک مفید وجود دارد. در Android Studio، "TODO" را در پایین فشار دهید. لیستی از همه TODOها در این Codelab را نشان می دهد. در بخش بعدی با اولین TODO شروع می کنیم.
5. با استفاده از اثر انگشت، اعتبارنامه را ثبت کنید
برای فعال کردن احراز هویت با استفاده از اثر انگشت، ابتدا باید یک اعتبار ایجاد شده توسط یک تأییدکننده پلتفرم تأییدکننده کاربر - یک تأییدکننده تعبیهشده در دستگاه که کاربر را با استفاده از بیومتریک، مانند حسگر اثر انگشت تأیید میکند، ثبت کنید.
همانطور که در بخش قبل دیدیم، دکمه اکشن شناور اکنون کاری انجام نمی دهد. بیایید ببینیم چگونه می توانیم یک اعتبار جدید ثبت کنیم.
با API سرور تماس بگیرید: /auth/registerRequest
AuthRepository.kt
را باز کنید و TODO(1) را پیدا کنید.
در اینجا registerRequest
متدی است که با فشار دادن FAB فراخوانی می شود. میخواهیم این روش API سرور را فراخوانی کند /auth/registerRequest
. API یک ApiResult
با تمام PublicKeyCredentialCreationOptions
که کلاینت برای ایجاد یک اعتبار جدید به آن نیاز دارد، برمی گرداند.
سپس می توانیم getRegisterPendingIntent
با گزینه ها فراخوانی کنیم. این FIDO2 API یک Android PendingIntent را برای باز کردن یک گفتگوی اثر انگشت و ایجاد یک اعتبار جدید برمیگرداند و ما میتوانیم آن 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) را پیدا کنید.
اینجاست که UI 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)
}
}
}
}
با API سرور تماس بگیرید: /auth/registerResponse
AuthRepository.kt
را باز کنید و TODO(4) را پیدا کنید.
این متد registerResponse
پس از اینکه UI با موفقیت یک اعتبار جدید را ایجاد کرد فراخوانی می شود و ما می خواهیم آن را به سرور ارسال کنیم.
شی PublicKeyCredential
اطلاعاتی در مورد اعتبار جدید ایجاد شده در داخل دارد. اکنون می خواهیم شناسه کلید محلی خود را به خاطر بسپاریم تا بتوانیم آن را از سایر کلیدهای ثبت شده در سرور متمایز کنیم. در شی PublicKeyCredential
، ویژگی rawId
آن را بگیرید و با استفاده از toBase64
در یک متغیر رشته محلی قرار دهید.
اکنون آماده ارسال اطلاعات به سرور هستیم. از api.registerResponse
برای فراخوانی API سرور و ارسال پاسخ استفاده کنید. مقدار برگشتی شامل لیستی از تمام اعتبارنامه های ثبت شده در سرور، از جمله اعتبار جدید است.
در نهایت، میتوانیم نتایج را در 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
هدایت می شود.
با API سرور تماس بگیرید: /auth/signinRequest
AuthRepository.kt
را باز کنید و TODO(5) را پیدا کنید.
این متد signinRequest
زمانی فراخوانی می شود که AuthFragment
باز شود. در اینجا، میخواهیم سرور را درخواست کنیم و ببینیم که آیا میتوانیم به کاربر اجازه دهیم با FIDO2 وارد شود.
ابتدا باید PublicKeyCredentialRequestOptions
از سرور بازیابی کنیم. برای فراخوانی API سرور از api.signInRequest
استفاده کنید. ApiResult
برگشتی حاوی PublicKeyCredentialRequestOptions
است.
با PublicKeyCredentialRequestOptions
، میتوانیم از FIDO2 API getSignIntent
برای ایجاد یک PendingIntent برای باز کردن گفتگوی اثر انگشت استفاده کنیم.
در نهایت، میتوانیم PendingIntent را به UI برگردانیم.
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)
}
}
}
}
API سرور را فراخوانی کنید: /auth/signinResponse
AuthRepository.kt
را باز کنید و TODO(8) را پیدا کنید.
شی PublicKeyCredential یک شناسه اعتبار در آن به عنوان keyHandle
دارد. درست همانطور که در جریان ثبت نام انجام دادیم، اجازه دهید این را در یک متغیر رشته محلی ذخیره کنیم تا بتوانیم بعداً آن را ذخیره کنیم.
اکنون آماده هستیم تا API سرور را با api.signinResponse
فراخوانی کنیم. مقدار بازگشتی حاوی لیستی از اعتبارنامه ها است.
در این مرحله، ورود به سیستم با موفقیت انجام می شود. ما باید تمام نتایج را در DataStore
خود ذخیره کنیم. لیست اعتبارنامه ها باید به عنوان StringSet با کلید CREDENTIALS
ذخیره شود. شناسه اعتبار محلی که در بالا ذخیره کردیم باید به عنوان یک رشته با کلید LOCAL_CREDENTIAL_ID
ذخیره شود.
در نهایت، باید وضعیت ورود به سیستم را بهروزرسانی کنیم تا رابط کاربری بتواند کاربر را به HomeFragment هدایت کند. این را می توان با انتشار یک شی SignInState.SignedIn
در SharedFlow با نام signInStateMutable
انجام داد. همچنین میخواهیم refreshCredentials
برای واکشی اعتبار کاربر فراخوانی کنیم تا در UI فهرست شوند.
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)
}
}
برنامه را اجرا کنید و روی "Reauth" کلیک کنید تا AuthFragment
باز شود. اکنون باید یک گفتگوی اثر انگشت را مشاهده کنید که از شما می خواهد با اثر انگشت خود وارد شوید.
تبریک میگم اکنون یاد گرفتید که چگونه از FIDO2 API در اندروید برای ثبت نام و ورود به سیستم استفاده کنید.
7. تبریک می گویم!
شما با موفقیت لبه کد را به پایان رساندید - اولین API FIDO2 Android شما .
چیزی که یاد گرفتی
- نحوه ثبت اعتبار با استفاده از تأیید کننده پلت فرم کاربر
- چگونه یک کاربر را با استفاده از احراز هویت ثبت شده احراز هویت کنیم.
- گزینه های موجود برای ثبت احراز هویت جدید.
- بهترین شیوه های UX برای احراز هویت مجدد با استفاده از حسگر بیومتریک.
مرحله بعدی
- یاد بگیرید که چگونه تجربه مشابهی را در یک وب سایت ایجاد کنید.
شما می توانید آن را با امتحان کردن اولین آزمایشگاه کد WebAuthn خود یاد بگیرید!
منابع
تشکر ویژه از Yuriy Ackermann از FIDO Alliance برای کمک شما.