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
- به ماژول CredentialManager رفته و پروژه را در اندروید استودیو باز کنید.
بیایید وضعیت اولیه برنامه را ببینیم
برای مشاهده نحوه عملکرد اولیه برنامه، مراحل زیر را دنبال کنید:
- برنامه را اجرا کنید.
- یک صفحه اصلی با دکمه ثبت نام و ورود مشاهده می کنید.
- برای ثبت نام با استفاده از رمز عبور یا رمز عبور می توانید روی sign up کلیک کنید.
- برای ورود به سیستم با استفاده از کلید عبور و رمز عبور ذخیره شده، می توانید روی ورود کلیک کنید.
برای درک اینکه کلیدهای عبور چیست و چگونه کار می کنند، به نحوه عملکرد کلیدهای عبور مراجعه کنید؟ .
3. امکان ثبت نام با استفاده از کلیدهای عبور را اضافه کنید
هنگام ثبت نام برای یک حساب جدید در یک برنامه Android که از Credential Manager API استفاده می کند، کاربران می توانند یک رمز عبور برای حساب خود ایجاد کنند. این کلید عبور بهطور ایمن در ارائهدهنده اعتبار انتخابی کاربر ذخیره میشود و برای ورود به سیستم در آینده استفاده میشود، بدون اینکه کاربر هر بار رمز عبور خود را وارد کند.
اکنون، با استفاده از بیومتریک/قفل صفحه، یک رمز عبور ایجاد میکنید و اعتبار کاربری را ثبت میکنید.
با رمز عبور ثبت نام کنید
در داخل Credential Manager -> app -> main -> java -> SignUpFragment.kt، می توانید یک فیلد متنی "نام کاربری" و یک دکمه برای ثبت نام با رمز عبور مشاهده کنید.
چالش و سایر پاسخهای json را به فراخوانی ()createPasskey ارسال کنید
قبل از ایجاد یک رمز عبور، باید از سرور درخواست کنید که اطلاعات لازم را در طول تماس CredentialCredential () به Credential Manager API ارسال کند.
خوشبختانه، شما در حال حاضر یک پاسخ ساختگی در دارایی های خود دارید ( RegFromServer.txt ) که چنین پارامترهایی را در این کد لبه برمی گرداند.
- در برنامه خود، به روش SignUpFragment.kt ، Find، 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 ثبت نام را از دارایی ها می خواند و 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 را از دارایی ها می خوانید.
- این json دارای 4 فیلد برای جایگزینی است.
- UserId باید منحصر به فرد باشد تا کاربر بتواند چندین کلید عبور ایجاد کند (در صورت نیاز). شما <userId> را با userId ایجاد شده جایگزین می کنید.
- <چالش> همچنین باید منحصر به فرد باشد، بنابراین شما یک چالش منحصر به فرد تصادفی ایجاد خواهید کرد. روش از قبل در کد شما موجود است.
قطعه کد زیر شامل گزینه های نمونه ای است که از سرور دریافت می کنید:
{
"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 بایتی ایجاد شده در هر حساب به خوبی کار خواهد کرد. | |
این فیلد باید یک شناسه منحصر به فرد برای حسابی داشته باشد که کاربر آن را تشخیص می دهد، مانند آدرس ایمیل یا نام کاربری خود. این در انتخابگر حساب نمایش داده می شود. (اگر از نام کاربری استفاده می کنید، از همان مقداری که در احراز هویت رمز عبور استفاده می کنید استفاده کنید.) | |
این فیلد یک نام اختیاری و کاربرپسندتر برای حساب است. این یک نام دلپذیر برای حساب کاربری است که فقط برای نمایش در نظر گرفته شده است. | |
نهاد متکی با جزئیات درخواست شما مطابقت دارد. به موارد زیر نیاز دارد:
| |
پارامترهای اعتبار کلید عمومی فهرستی از الگوریتم ها و انواع کلیدهای مجاز است. این لیست باید حداقل دارای یک عنصر باشد. | |
کاربری که سعی در ثبت یک دستگاه دارد ممکن است دستگاه های دیگری را ثبت کرده باشد. برای محدود کردن ایجاد چندین اعتبار برای یک حساب کاربری در یک احراز هویت، میتوانید این دستگاهها را نادیده بگیرید. عضو حمل و نقل ، در صورت ارائه، باید حاوی نتیجه فراخوانی getTransports () در هنگام ثبت هر اعتبار باشد. | |
نشان می دهد که آیا دستگاه باید روی پلت فرم وصل شود یا خیر یا اینکه هیچ الزامی در مورد آن وجود ندارد. آن را روی "پلتفرم" تنظیم کنید. این نشان میدهد که ما یک احراز هویت میخواهیم که در دستگاه پلتفرم تعبیه شده باشد، و از کاربر خواسته نمیشود که مثلاً یک کلید امنیتی USB را وارد کند. | |
| مقدار "لازم" برای ایجاد یک رمز عبور را نشان می دهد. |
اعتبارنامه ایجاد کنید
- هنگامی که یک 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 یا دستور debug adb بررسی کنید
- اکنون، در نهایت باید با ارسال اعتبار کلید عمومی به سرور و اجازه ورود کاربر، فرآیند ثبت نام را تکمیل کنید. برنامه یک شی اعتبار دریافت می کند که حاوی یک کلید عمومی است که می توانید برای ثبت رمز عبور به سرور ارسال کنید.
در اینجا، ما از یک سرور ساختگی استفاده کردهایم، بنابراین فقط 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 نشان دهنده (ساختار) سرور کلید عمومی را برای استفاده در آینده ذخیره کرده است.
- شما SignedInThroughPasskeys را به عنوان درست علامت گذاری می کنید، که نشان می دهد از طریق کلیدهای عبور وارد سیستم می شوید.
- پس از ورود به سیستم، کاربر خود را به صفحه اصلی هدایت می کنید.
قطعه کد زیر شامل یک نمونه گزینه است که باید دریافت کنید:
{
"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 را برای ایجاد فراخوانی Password جایگزین کنید:
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(request, requireActivity()) 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 احراز هویت را برای بازیابی همه کلیدهای عبور مرتبط با این حساب کاربری بازمی گرداند.
پارامتر دوم: clientDataHash - یک هش که برای تأیید هویت طرف متکی استفاده می شود، تنها در صورتی تنظیم می شود که GetCredentialRequest.origin را تنظیم کرده باشید. برای برنامه نمونه، این صفر است.
اگر ترجیح میدهید بهجای بازگشت به کشف اعتبارنامههای راه دور، عملیات فوراً زمانی که اعتباری در دسترس نیست بازگردد، پارامتر سوم درست است و در غیر این صورت، false (پیشفرض) است.
- متد 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(). این فهرستی از گزینههای اعتبار و یک زمینه فعالیت را برای نمایش گزینههای صفحه پایین در آن زمینه میگیرد.
- پس از موفقیت آمیز بودن درخواست، صفحه پایینی را در صفحه نمایش خود مشاهده می کنید که تمام اعتبارنامه های ایجاد شده برای حساب مرتبط را فهرست می کند.
- اکنون کاربران می توانند هویت خود را از طریق بیومتریک یا قفل صفحه و غیره تأیید کنند تا اعتبار انتخاب شده را تأیید کنند.
- شما SignedInThroughPasskeys را به عنوان درست علامت گذاری می کنید، که نشان می دهد از طریق کلیدهای عبور وارد سیستم می شوید. در غیر این صورت نادرست.
- شما نمایان بودن نماهای رندر شده را کنترل میکنید و در صورت عدم موفقیت یا ناموفق بودن درخواست به دلایلی، استثنائات را مدیریت میکنید. در اینجا پیام های خطا ثبت شده و در برنامه در یک گفتگوی خطا نشان داده می شوند. میتوانید گزارشهای کامل خطا را از طریق Android Studio یا دستور debug adb بررسی کنید
- اکنون، در نهایت باید با ارسال اعتبار کلید عمومی به سرور و اجازه ورود کاربر، فرآیند ثبت نام را تکمیل کنید. برنامه یک شی اعتبار دریافت می کند که حاوی یک کلید عمومی است که می توانید آن را برای احراز هویت از طریق کلید عبور به سرور ارسال کنید.
در اینجا، ما از یک سرور ساختگی استفاده کردهایم، بنابراین فقط 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() درست را نشان می دهد که نشان می دهد سرور (ساختار) کلید عمومی را برای استفاده در آینده تایید کرده است.
- پس از ورود به سیستم، کاربر خود را به صفحه اصلی هدایت می کنید.
قطعه کد زیر شامل یک نمونه شی PublicKeyCredential
است:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
جدول زیر جامع نیست، اما شامل پارامترهای مهم در شی PublicKeyCredential
است:
پارامترها | توضیحات |
شناسه رمزگذاری شده Base64URL اعتبار کلید عبور تأیید شده. | |
یک نسخه شی | |
یک شی | |
یک شی | |
یک شی | |
یک شی |
برنامه را اجرا کنید، برای ورود به سیستم -> با کلیدهای عبور/رمز عبور ذخیره شده وارد شوید و با استفاده از اطلاعات کاربری ذخیره شده وارد شوید.
آن را امتحان کنید
ایجاد کلیدهای عبور، ذخیره گذرواژه در Credential Manager، و احراز هویت از طریق کلیدهای عبور یا رمز عبور ذخیره شده را با استفاده از Credential Manager API در برنامه Android خود اجرا کردید.
6. تبریک می گویم!
شما این کد لبه را تمام کردید! اگر میخواهید وضوح نهایی را بررسی کنید، در https://github.com/android/identity-samples/tree/main/CredentialManager موجود است.
اگر سؤالی دارید، از آنها در StackOverflow با یک برچسب passkey
بپرسید.