1. قبل از شروع
راه حل های احراز هویت سنتی تعدادی از چالش های امنیتی و قابلیت استفاده را ایجاد می کنند.
رمز عبور به طور گسترده استفاده می شود اما ...
- به راحتی فراموش می شود
- کاربران برای ایجاد رمزهای عبور قوی نیاز به دانش دارند.
- فیش کردن، برداشت و پخش مجدد توسط مهاجمان آسان است.
Android روی ایجاد Credential Manager API کار کرده است تا تجربه ورود به سیستم را ساده کند و خطرات امنیتی را با پشتیبانی از کلیدهای عبور ، نسل بعدی استاندارد صنعتی برای احراز هویت بدون رمز عبور، برطرف کند.
Credential Manager پشتیبانی از کلیدهای عبور را گرد هم می آورد و آن را با روش های احراز هویت سنتی مانند گذرواژه ها، ورود به سیستم با Google و غیره ترکیب می کند.
کاربران میتوانند کلیدهای عبور ایجاد کنند، آنها را در Google Password Manager ذخیره کنند، که این کلیدهای عبور را در دستگاههای Android که کاربر در آن وارد شده است همگامسازی میکند. یک کلید عبور باید ایجاد شود، با یک حساب کاربری مرتبط شود، و کلید عمومی آن ذخیره شود. قبل از اینکه کاربر بتواند با آن وارد سرور شود.
در این کد لبه، یاد خواهید گرفت که چگونه با استفاده از کلیدهای عبور و رمز عبور با استفاده از Credential Manager API ثبت نام کنید و از آنها برای اهداف احراز هویت آینده استفاده کنید. 2 جریان وجود دارد که عبارتند از:
- ثبت نام: با استفاده از کلیدهای عبور و رمز عبور.
- ورود به سیستم: با استفاده از کلیدهای عبور و رمز عبور ذخیره شده.
پیش نیازها
- آشنایی اولیه با نحوه اجرای برنامه ها در اندروید استودیو.
- درک اولیه از جریان احراز هویت در برنامه های اندروید.
- درک اولیه از کلیدهای عبور .
چیزی که یاد خواهید گرفت
- نحوه ایجاد رمز عبور
- نحوه ذخیره رمز عبور در پسورد منیجر
- نحوه احراز هویت کاربران با رمز عبور یا رمز عبور ذخیره شده
آنچه شما نیاز دارید
یکی از ترکیب دستگاه های زیر:
- دستگاه اندرویدی که اندروید 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 رفته و پروژه را در اندروید استودیو باز کنید.
بیایید وضعیت اولیه برنامه را ببینیم
برای مشاهده نحوه عملکرد اولیه برنامه، مراحل زیر را دنبال کنید:
- برنامه را راه اندازی کنید.
- یک صفحه اصلی با دکمه ثبت نام و ورود مشاهده می کنید. این دکمهها هنوز هیچ کاری انجام نمیدهند، اما عملکرد آنها را در بخشهای آینده فعال خواهیم کرد.
3. امکان ثبت نام با استفاده از کلیدهای عبور را اضافه کنید
هنگام ثبت نام برای یک حساب جدید در یک برنامه Android که از Credential Manager API استفاده می کند، کاربران می توانند یک رمز عبور برای حساب خود ایجاد کنند. این کلید عبور بهطور ایمن در ارائهدهنده اعتبار انتخابی کاربر ذخیره میشود و برای ورود به سیستم در آینده استفاده میشود، بدون اینکه کاربر هر بار رمز عبور خود را وارد کند.
اکنون، با استفاده از بیومتریک/قفل صفحه، یک رمز عبور ایجاد میکنید و اعتبار کاربری را ثبت میکنید.
با رمز عبور ثبت نام کنید
کد داخل Credential Manager/app/main/java/SignUpFragment.kt، یک فیلد متنی "نام کاربری" و یک دکمه برای ثبت نام با یک رمز عبور تعریف می کند.
چالش و سایر پاسخهای json را به یک فراخوانی ()createPasskey ارسال کنید
قبل از ایجاد رمز عبور، باید اطلاعات لازم را از سرور درخواست کنید تا در طول تماس CredentialCredential () به API Credential Manager منتقل شود.
شما در حال حاضر یک پاسخ ساختگی در دارایی های پروژه خود به نام 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 از کلید عبور ایجاد شده. این شناسه به مرورگر کمک میکند تا تشخیص دهد که آیا رمز عبور منطبق در دستگاه پس از احراز هویت وجود دارد یا خیر. این مقدار باید در پایگاه داده در backend ذخیره شود. | |
یک نسخه شی | |
یک شی | |
یک شیء تصدیق رمزگذاری شده |
برنامه را اجرا کنید و می توانید روی دکمه Sign up with passkeys کلیک کنید و یک رمز عبور ایجاد کنید.
4. رمز عبور را در Credential Provider ذخیره کنید
در این برنامه، در داخل صفحه ثبت نام خود، از قبل یک ثبت نام با نام کاربری و رمز عبور دارید که برای اهداف نمایشی پیاده سازی شده است.
برای ذخیره اعتبار رمز عبور کاربر با ارائه دهنده رمز عبور خود، یک 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() API ارسال می شود. قطعه کد زیر شامل چند گزینه نمونه است که می توانید در یک پاسخ واقعی دریافت کنید:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
جدول زیر برخی از پارامترهای مهم در یک شی PublicKeyCredentialRequestOptions
را توضیح می دهد:
پارامترها | توضیحات |
یک چالش ایجاد شده توسط سرور در یک شی | |
شناسه RP یک دامنه است. یک وب سایت می تواند دامنه یا پسوند قابل ثبت خود را مشخص کند. این مقدار باید با پارامتر |
- در مرحله بعد باید یک شی
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()
درست را نشان می دهد که نشان می دهد سرور (ساختار) کلید عمومی را برای استفاده در آینده تایید کرده است.- پس از ورود به سیستم، کاربر خود را به صفحه اصلی هدایت می کنید.
برنامه را اجرا کنید و برای ورود به سیستم > با کلیدهای عبور/گذرواژه ذخیره شده وارد شوید و سعی کنید با استفاده از اطلاعات کاربری ذخیره شده وارد شوید.
آن را امتحان کنید
ایجاد کلیدهای عبور، ذخیره گذرواژه در Credential Manager، و احراز هویت از طریق کلیدهای عبور یا رمز عبور ذخیره شده را با استفاده از Credential Manager API در برنامه Android خود اجرا کردید.
6. تبریک می گویم!
شما این کد لبه را تمام کردید! اگر می خواهید راه حل نهایی را بررسی کنید، که در https://github.com/android/identity-samples/tree/main/CredentialManager موجود است
اگر سؤالی دارید، از آنها در StackOverflow با یک برچسب passkey
بپرسید.