1. قبل از شروع
در این کد لبه یاد می گیرید که چگونه با استفاده از Credential Manager در اندروید Sign in with Google را پیاده سازی کنید.
پیش نیازها
- درک اولیه استفاده از Kotlin برای توسعه اندروید
- درک اولیه Jetpack Compose (اطلاعات بیشتر را می توانید در اینجا بیابید)
چیزی که یاد خواهید گرفت
- چگونه یک پروژه Google Cloud ایجاد کنیم
- نحوه ایجاد مشتریان OAuth در Google Cloud Console
- نحوه اجرای Sign in With Google با استفاده از جریان پایین صفحه
- نحوه اجرای Sign in With Google با استفاده از جریان دکمه
آنچه شما نیاز دارید
- اندروید استودیو ( از اینجا دانلود کنید)
- رایانه ای که الزامات سیستم Android Studio را برآورده می کند
- رایانه ای که الزامات سیستم شبیه ساز اندروید را برآورده می کند
2. یک پروژه اندروید استودیو ایجاد کنید
مدت زمان 3:00 - 5:00
برای شروع، باید یک پروژه جدید در Android Studio ایجاد کنیم:
- اندروید استودیو را باز کنید
- روی New Project کلیک کنید
- Phone and Tablet and Empty Activity را انتخاب کنید
- روی Next کلیک کنید
- اکنون زمان تنظیم چند قطعه از پروژه است:
- نام : این نام پروژه شماست
- نام بسته : این بسته به صورت خودکار بر اساس نام پروژه شما پر می شود
- ذخیره مکان : این باید به طور پیش فرض پوشه ای باشد که Android Studio پروژه های شما را در آن ذخیره می کند. می توانید آن را به هر جایی که می خواهید تغییر دهید.
- حداقل SDK : این پایین ترین نسخه Android SDK است که برنامه شما برای اجرا ساخته شده است. در این CodeLab از API 36 (باقلوا) استفاده خواهیم کرد.
- روی Finish کلیک کنید
- Android Studio پروژه را ایجاد می کند و هر وابستگی لازم را برای برنامه پایه دانلود می کند، این می تواند چند دقیقه طول بکشد. برای مشاهده این اتفاق، فقط روی نماد ساخت کلیک کنید:
- پس از تکمیل، Android Studio باید شبیه به این باشد:
3. پروژه Google Cloud خود را راه اندازی کنید
یک پروژه Google Cloud ایجاد کنید
- به Google Cloud Console بروید
- پروژه خود را باز کنید یا یک پروژه جدید ایجاد کنید
- روی APIs & Services کلیک کنید
- به صفحه رضایت OAuth بروید
- برای ادامه، باید فیلدهای نمای کلی را پر کنید. برای شروع پر کردن این اطلاعات روی Get Start کلیک کنید:
- نام برنامه : نام این برنامه، که باید همان چیزی باشد که هنگام ایجاد پروژه در Android Studio استفاده کرده اید
- ایمیل پشتیبانی کاربر : این نشان می دهد که حساب Google شما با آن وارد سیستم شده اید و هر گروه Google که مدیریت می کنید
- مخاطب :
- داخلی برای برنامهای که فقط در سازمان شما استفاده میشود. اگر سازمانی مرتبط با Google Cloud Project ندارید، نمیتوانید این را انتخاب کنید.
- خارجی همان چیزی است که ما استفاده می کنیم.
- اطلاعات تماس : این می تواند هر ایمیلی را داشته باشد که می خواهید نقطه تماس برنامه باشد
- خدمات Google API: سیاست داده کاربر را مرور کنید.
- پس از بررسی و موافقت با خط مشی داده های کاربر ، روی ایجاد کلیک کنید
مشتریان OAuth را تنظیم کنید
اکنون که یک Google Cloud Project راهاندازی کردهایم، باید یک Web Client و Android Client اضافه کنیم تا بتوانیم با شناسههای کلاینت آنها با سرور Backend OAuth تماسهای API برقرار کنیم.
برای Android Web Client، شما نیاز دارید:
- نام بسته برنامه شما (به عنوان مثال com.example.example)
- امضای SHA-1 برنامه شما
- SHA-1 Signature چیست؟
- اثر انگشت SHA-1 یک هش رمزنگاری است که از کلید امضای برنامه شما ایجاد میشود. این به عنوان یک شناسه منحصر به فرد برای گواهی امضای برنامه خاص شما عمل می کند. به آن مانند یک "امضای" دیجیتال برای برنامه خود فکر کنید.
- چرا به امضای SHA-1 نیاز داریم؟
- اثر انگشت SHA-1 تضمین میکند که فقط برنامه شما، که با کلید امضای خاص شما امضا شده است، میتواند با استفاده از شناسه مشتری OAuth 2.0، توکنهای دسترسی را درخواست کند و از دسترسی سایر برنامهها (حتی آنهایی که نام بسته مشابه دارند) به منابع پروژه و دادههای کاربر شما جلوگیری میکند.
- اینجوری فکر کن:
- کلید امضای برنامه شما مانند کلید فیزیکی "در" برنامه شما است. این چیزی است که امکان دسترسی به عملکرد داخلی برنامه را فراهم می کند.
- اثر انگشت SHA-1 مانند یک شناسه کارت کلید منحصر به فرد است که به کلید فیزیکی شما مرتبط است. این یک کد خاص است که آن کلید خاص را شناسایی می کند.
- شناسه مشتری OAuth 2.0 مانند یک کد ورودی به یک منبع یا سرویس خاص Google است (به عنوان مثال Google Sign-in).
- وقتی اثر انگشت SHA-1 را در طول راهاندازی سرویس گیرنده OAuth ارائه میکنید، اساساً به Google میگویید: «فقط کارت کلید با این شناسه خاص (SHA-1) میتواند این کد دسترسی (شناسه مشتری) را باز کند.» این اطمینان حاصل می کند که فقط برنامه شما می تواند به خدمات Google مرتبط با آن کد ورودی دسترسی داشته باشد."
- SHA-1 Signature چیست؟
برای Web Client، تنها چیزی که نیاز داریم نامی است که میخواهید برای شناسایی مشتری در کنسول استفاده کنید.
کلاینت Android OAuth 2.0 را ایجاد کنید
- به صفحه مشتریان بروید
- روی Create Client کلیک کنید
- اندروید را برای نوع Application انتخاب کنید
- شما باید نام بسته برنامه خود را مشخص کنید
- از Android Studio باید امضای SHA-1 برنامه خود را دریافت کرده و آن را در اینجا کپی/پیست کنید:
- به اندروید استودیو بروید و ترمینال را باز کنید
- این دستور را اجرا کنید:
این دستور برای فهرست کردن جزئیات یک ورودی خاص (نام مستعار) در یک فروشگاه کلید طراحی شده است.keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
-
-list
: این گزینه به keytool میگوید محتویات keystore را فهرست کند. -
-v
: این گزینه خروجی مفصل را فعال می کند و اطلاعات دقیق تری در مورد ورودی ارائه می دهد. -
-keystore ~/.android/debug.keystore
: مسیر فایل keystore را مشخص می کند. -
-alias androiddebugkey
: نام مستعار (نام ورودی) کلیدی را که می خواهید بررسی کنید مشخص می کند. -
-storepass android
: رمز عبور فایل keystore را ارائه می دهد. -
-keypass android
: رمز عبور کلید خصوصی نام مستعار مشخص شده را ارائه می دهد.
-
- مقدار امضای SHA-1 را کپی کنید:
- به پنجره Google Cloud برگردید و مقدار امضای SHA-1 را جایگذاری کنید:
- اکنون صفحه شما باید شبیه به این باشد و می توانید روی ایجاد کلیک کنید:
سرویس گیرنده وب OAuth 2.0 ایجاد کنید
- برای ایجاد شناسه سرویس گیرنده برنامه وب، مراحل 1-2 را از قسمت Create Android Client تکرار کنید و Web Application را برای نوع برنامه انتخاب کنید.
- به مشتری یک نام بدهید (این مشتری OAuth خواهد بود):
- روی ایجاد کلیک کنید
- پیش بروید و شناسه مشتری را از پنجره بازشو کپی کنید، بعداً به آن نیاز خواهید داشت
اکنون که ما کلاینتهای OAuth را راهاندازی کردهایم، میتوانیم به Android Studio برگردیم و برنامه Sign in With Google Android خود را بسازیم!
4. دستگاه مجازی اندروید را راه اندازی کنید
برای آزمایش سریع برنامه خود بدون دستگاه اندروید فیزیکی، می خواهید یک دستگاه مجازی اندروید بسازید که بتوانید آن را بسازید و بلافاصله برنامه خود را از Android Studio اجرا کنید. اگر میخواهید با یک دستگاه اندروید فیزیکی تست کنید، میتوانید دستورالعملهای مستندات توسعهدهنده اندروید را دنبال کنید
یک دستگاه مجازی اندروید بسازید
- در Android Studio، Device Manager را باز کنید
- روی دکمه + > Create Virtual Device کلیک کنید
- از اینجا می توانید هر دستگاهی را که برای پروژه خود نیاز دارید اضافه کنید. برای اهداف این Codelab، تلفن متوسط را انتخاب کنید سپس روی Next کلیک کنید
- اکنون میتوانید با دادن یک نام منحصر به فرد، انتخاب نسخه اندرویدی که دستگاه اجرا میشود و موارد دیگر، دستگاه را برای پروژه خود پیکربندی کنید. اطمینان حاصل کنید که API روی API 36 "باقلوا" تنظیم شده است. اندروید 16 سپس روی Finish کلیک کنید
- باید ببینید دستگاه جدید در Device Manager نمایش داده می شود. برای تأیید اینکه دستگاه اجرا می شود، ادامه دهید و کلیک کنید
در کنار دستگاهی که به تازگی ایجاد کرده اید
- دستگاه باید اکنون در حال اجرا باشد!
وارد دستگاه مجازی اندروید شوید
دستگاهی که به تازگی ایجاد کردید کار می کند، اکنون برای جلوگیری از خطا در هنگام آزمایش ورود به سیستم با Google، باید با یک حساب Google وارد دستگاه شویم.
- به تنظیمات بروید:
- روی مرکز صفحه در دستگاه مجازی کلیک کنید و انگشت خود را به سمت بالا بکشید
- به دنبال اپلیکیشن Settings بگردید و روی آن کلیک کنید
- روی Google در تنظیمات کلیک کنید
- روی Sign in کلیک کنید و دستورات را دنبال کنید تا به حساب Google خود وارد شوید
- اکنون باید در دستگاه وارد سیستم شوید
دستگاه اندروید مجازی شما اکنون برای آزمایش آماده است!
5. وابستگی ها را اضافه کنید
مدت زمان 5:00
برای برقراری تماسهای OAuth API، ابتدا باید کتابخانههای مورد نیاز را ادغام کنیم که به ما امکان میدهند درخواستهای احراز هویت را انجام دهیم و از شناسههای Google برای انجام آن درخواستها استفاده کنیم:
- libs.googleid
- libs.play.services.auth
- به File > Project Structure بروید:
- سپس به Dependencies > app > '+' > Library Dependency بروید
- اکنون باید کتابخانه های خود را اضافه کنیم:
- در گفتگوی جستجو، googleid را تایپ کنید و روی جستجو کلیک کنید
- فقط باید یک ورودی وجود داشته باشد، ادامه دهید و آن را انتخاب کنید و بالاترین نسخه موجود را انتخاب کنید (در زمان این Codelab itis 1.1.1)
- روی OK کلیک کنید
- مراحل 1-3 را تکرار کنید اما به جای آن عبارت "play-services-auth" را جستجو کنید و خط را با "com.google.android.gms" به عنوان شناسه گروه و "play-services-auth" به عنوان نام Artifact انتخاب کنید.
- روی OK کلیک کنید
6. جریان ورق پایین
جریان صفحه پایین از Credential Manager API استفاده میکند تا راهی ساده برای ورود کاربران به برنامه شما با استفاده از حسابهای Google خود در Android باشد . طراحی شده است تا سریع و راحت باشد، به خصوص برای کاربران بازگشتی. این جریان باید در راه اندازی برنامه فعال شود.
درخواست ورود به سیستم را بسازید
- برای شروع، ادامه دهید و توابع
Greeting()
وGreetingPreview()
را ازMainActivity.kt
حذف کنید، ما به آنها نیازی نخواهیم داشت. - اکنون باید مطمئن شویم که بسته های مورد نیاز ما برای این پروژه وارد شده است. ادامه دهید و عبارات
import
زیر را بعد از موارد موجود در خط 3 اضافه کنید:import android.content.ContentValues.TAG import android.content.Context import android.credentials.GetCredentialException import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.clickable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.NoCredentialException import androidx.credentials.GetCredentialRequest import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.CoroutineScope import androidx.compose.runtime.LaunchedEffect import kotlinx.coroutines.delay import kotlinx.coroutines.launch
- بعد، ما باید تابع خود را برای ایجاد درخواست Bottom Sheet ایجاد کنیم. این کد را در زیر کلاس MainActivity قرار دهید
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
//We will build out this function in a moment
signIn(requestFalse, context)
}
}
}
//This function is used to generate a secure nonce to pass in with our request
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
بیایید کار این کد را به تفکیک شرح دهیم:
fun BottomSheet(webClientId: String) {...}
: تابعی به نام BottomSheet ایجاد می کند که یک آرگومان رشته ای به نام webClientid را می گیرد.
-
val context = LocalContext.current
: زمینه Android فعلی را بازیابی می کند. این برای عملیات های مختلف، از جمله راه اندازی مؤلفه های UI مورد نیاز است. -
LaunchedEffect(Unit) { ... }
:LaunchedEffect
یک Jetpack Compose Composable است که به شما امکان می دهد یک تابع تعلیق (عملکردی که می تواند متوقف شود و اجرا را از سر بگیرد) در چرخه حیات composable اجرا کنید. واحد بهعنوان کلید به این معنی است که این افکت تنها یک بار زمانی که composable برای اولین بار راهاندازی میشود اجرا میشود.-
val googleIdOption: GetGoogleIdOption = ...
: یک شیGetGoogleIdOption
ایجاد می کند. این شیء نوع اعتبار درخواست شده از Google را پیکربندی می کند.-
.Builder()
: یک الگوی سازنده برای پیکربندی گزینه ها استفاده می شود. -
.setFilterByAuthorizedAccounts(true)
: مشخص می کند که آیا به کاربر اجازه می دهد از بین تمام حساب های Google یا فقط حساب هایی که قبلاً برنامه را مجاز کرده اند، انتخاب کند. در این حالت، روی true تنظیم میشود، به این معنی که درخواست با استفاده از اعتبارنامههایی که کاربر قبلاً برای استفاده با این برنامه مجوز داده است، در صورت موجود بودن، انجام میشود. -
.setServerClientId(webClientId)
: شناسه سرویس گیرنده سرور را تنظیم می کند که یک شناسه منحصر به فرد برای باطن برنامه شما است. این مورد برای دریافت شناسه شناسه الزامی است. -
.setNonce(generateSecureRandomNonce())
: یک nonce، یک مقدار تصادفی را برای جلوگیری از حملات مجدد و اطمینان از مرتبط بودن رمز ID با درخواست خاص تنظیم می کند. -
.build()
: شیGetGoogleIdOption
را با پیکربندی مشخص شده ایجاد می کند.
-
-
val request: GetCredentialRequest = ...
: یک شیGetCredentialRequest
ایجاد می کند. این شیء کل درخواست اعتبار را کپسوله می کند.-
.Builder()
: الگوی سازنده را برای پیکربندی درخواست شروع می کند. -
.addCredentialOption(googleIdOption)
: googleIdOption را به درخواست اضافه می کند و مشخص می کند که ما می خواهیم یک رمز Google ID درخواست کنیم. -
.build()
: شیGetCredentialRequest
را ایجاد می کند.
-
-
val e = signIn(request, context)
: این تلاش برای ورود کاربر با درخواست ایجاد شده و زمینه فعلی است. نتیجه تابع signIn در e ذخیره می شود. این متغیر شامل یا نتیجه موفقیت آمیز یا یک استثنا خواهد بود. -
if (e is NoCredentialException) { ... }
: این یک بررسی مشروط است. اگر تابع signIn با NoCredentialException ناموفق باشد، به این معنی است که هیچ حساب قبلاً مجاز در دسترس نیست.-
val googleIdOptionFalse: GetGoogleIdOption = ...
: اگرsignIn
قبلی ناموفق بود، این بخش یکGetGoogleIdOption
جدید ایجاد می کند. -
.setFilterByAuthorizedAccounts(false)
: این تفاوت اساسی با گزینه اول است. فیلتر کردن حسابهای مجاز را غیرفعال میکند، به این معنی که از هر حساب Google در دستگاه میتوان برای ورود استفاده کرد. -
val requestFalse: GetCredentialRequest = ...
: یکGetCredentialRequest
جدید باgoogleIdOptionFalse
ایجاد می شود. -
signIn(requestFalse, context)
: این تلاش میکند تا کاربر را با درخواست جدیدی که اجازه استفاده از هر حسابی را میدهد به سیستم وارد کند.
-
-
در اصل، این کد با استفاده از پیکربندی های ارائه شده، درخواستی را به Credential Manager API برای بازیابی توکن Google ID برای کاربر آماده می کند. سپس میتوان از GetCredentialRequest برای راهاندازی رابط کاربری مدیر اعتبار استفاده کرد، جایی که کاربر میتواند حساب Google خود را انتخاب کرده و مجوزهای لازم را بدهد.
fun generateSecureRandomNonce(byteLength: Int = 32): String
: تابعی به نام generateSecureRandomNonce
را تعریف می کند. آرگومان عدد صحیح byteLength (با مقدار پیش فرض 32) را می پذیرد که طول مورد نظر nonce را بر حسب بایت مشخص می کند. رشته ای را برمی گرداند که نمایش کدگذاری شده با Base64 از بایت های تصادفی خواهد بود.
-
val randomBytes = ByteArray(byteLength)
: یک آرایه بایتی از بایت طول مشخص شده برای نگهداری بایت های تصادفی ایجاد می کند. -
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
:-
SecureRandom.getInstanceStrong()
: این یک مولد اعداد تصادفی قوی از نظر رمزنگاری به دست می آورد. این برای امنیت بسیار مهم است، زیرا تضمین می کند که اعداد تولید شده واقعا تصادفی هستند و قابل پیش بینی نیستند. از قوی ترین منبع موجود آنتروپی در سیستم استفاده می کند. -
.nextBytes(randomBytes)
: این آرایه تصادفی بایت را با بایت های تصادفی تولید شده توسط نمونه SecureRandom پر می کند.
-
-
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
:-
Base64.getUrlEncoder()
: این یک رمزگذار Base64 دریافت می کند که از الفبای ایمن URL استفاده می کند (با استفاده از - و _ به جای + و /). این مهم است زیرا تضمین می کند که رشته حاصل می تواند به طور ایمن در URL ها بدون نیاز به رمزگذاری بیشتر استفاده شود. -
.withoutPadding()
: این کاراکترهای padding را از رشته کدگذاری شده Base64 حذف می کند. این اغلب مطلوب است تا نونها کمی کوتاهتر و فشرده تر شوند. -
.encodeToString(randomBytes)
: این randomBytes را در یک رشته Base64 رمزگذاری می کند و آن را برمی گرداند.
-
به طور خلاصه، این تابع یک nonce تصادفی رمزنگاری قوی با طول مشخص تولید میکند، آن را با استفاده از Base64 ایمن URL کدگذاری میکند و رشته حاصل را برمیگرداند. این یک روش استاندارد برای تولید nonces است که برای استفاده در زمینههای حساس امنیتی ایمن هستند.
درخواست ورود به سیستم را انجام دهید
اکنون که میتوانیم درخواست ورود به سیستم خود را بسازیم، میتوانیم از Credential Manager برای استفاده از آن برای ورود به سیستم استفاده کنیم. برای انجام این کار، باید تابعی ایجاد کنیم که درخواستهای ورود به سیستم را با استفاده از Credential Manager رسیدگی کند، در حالی که استثناهای رایجی را که ممکن است با آنها مواجه شویم، مدیریت میکند.
برای انجام این کار می توانید این تابع را در زیر تابع BottomSheet()
قرار دهید.
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}
حال به تفکیک آنچه که کد در اینجا انجام می دهد:
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?
: این یک تابع تعلیق به نام signIn را تعریف می کند. این بدان معناست که میتوان بدون مسدود کردن رشته اصلی مکث کرد و از سر گرفت. آیا یک Exception?
که در صورت موفقیت آمیز بودن ورود به سیستم باطل خواهد بود یا در صورت عدم موفقیت ورود، استثناء خاص.
دو پارامتر نیاز دارد:
-
request
: یک شیGetCredentialRequest
، که حاوی پیکربندی برای نوع اعتبار برای بازیابی است (به عنوان مثال، Google ID). -
context
: زمینه Android مورد نیاز برای تعامل با سیستم.
برای بدنه تابع:
-
val credentialManager = CredentialManager.create(context)
: نمونه ای از CredentialManager را ایجاد می کند که رابط اصلی برای تعامل با Credential Manager API است. به این ترتیب برنامه جریان ورود به سیستم را شروع می کند. -
val failureMessage = "Sign in failed!"
: رشته ای (failureMessage) را تعریف می کند که در صورت شکست ورود به سیستم در یک نان تست نمایش داده شود. -
var e: Exception? = null
: این خط یک متغیر e را مقداردهی اولیه می کند تا هر استثنایی که ممکن است در طول فرآیند اتفاق بیفتد را ذخیره کند و با null شروع شود. -
delay(250)
: تاخیر 250 میلی ثانیه ای را معرفی می کند. این یک راه حل برای یک مشکل بالقوه است که در آن NoCredentialException ممکن است بلافاصله هنگام شروع برنامه پرتاب شود، به خصوص هنگام استفاده از جریان BottomSheet. این به سیستم زمان می دهد تا مدیر اعتبار را مقداردهی اولیه کند. -
try { ... } catch (e: Exception) { ... }
:یک بلوک try-catch برای مدیریت قوی خطا استفاده می شود. این تضمین میکند که اگر در طول فرآیند ورود به سیستم خطایی رخ دهد، برنامه خراب نمیشود و میتواند استثنا را به خوبی مدیریت کند.-
val result = credentialManager.getCredential(request = request, context = context)
: اینجا جایی است که تماس واقعی با Credential Manager API اتفاق میافتد و فرآیند بازیابی اعتبار را آغاز میکند. این درخواست و زمینه را به عنوان ورودی می گیرد و یک رابط کاربری برای انتخاب یک اعتبار به کاربر ارائه می دهد. در صورت موفقیت آمیز بودن، نتیجه ای حاوی اعتبار انتخاب شده را برمی گرداند. نتیجه این عملیات،GetCredentialResponse
، در متغیرresult
ذخیره می شود. -
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
: یک پیام تست کوتاه را نشان می دهد که نشان می دهد ورود به سیستم با موفقیت انجام شده است. -
Log.i(TAG, "Sign in Successful!")
: یک پیام سرگرم کننده و موفق را به logcat ثبت می کند. -
catch (e: GetCredentialException)
: موارد استثنایی از نوعGetCredentialException
کنترل می کند. این یک کلاس والد برای چندین استثنا خاص است که می تواند در طول فرآیند واکشی اعتبار رخ دهد. -
catch (e: GoogleIdTokenParsingException)
: استثناهایی را که هنگام تجزیه شناسه شناسه Google خطایی رخ می دهد کنترل می کند. -
catch (e: NoCredentialException)
:NoCredentialException
را کنترل میکند، که وقتی هیچ اعتبارنامهای برای کاربر در دسترس نباشد (مثلاً هیچ اعتبارنامهای را ذخیره نکردهاند یا حساب Google ندارند) پرتاب میشود.- مهمتر از همه، این تابع استثنای ذخیره شده در
e
،NoCredentialException
را برمی گرداند، و به تماس گیرنده اجازه می دهد تا در صورت عدم وجود اعتبار، مورد خاص را مدیریت کند.
- مهمتر از همه، این تابع استثنای ذخیره شده در
-
catch (e: GetCredentialCustomException)
: استثناهای سفارشی را که ممکن است توسط ارائهدهنده اعتبارنامه ارسال شود را کنترل میکند. -
catch (e: GetCredentialCancellationException)
:GetCredentialCancellationException
را کنترل می کند که زمانی که کاربر فرآیند ورود به سیستم را لغو می کند پرتاب می شود. -
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
: یک پیام نان تست را نمایش می دهد که نشان می دهد ورود به سیستم با استفاده از شکستMessage انجام نشد. -
Log.e(TAG, "", e)
: با استفاده از Log.e که برای خطاها استفاده می شود، استثنای لاگ کت اندروید را ثبت می کند. این شامل stacktrace استثنا برای کمک به اشکال زدایی می شود. همچنین شامل شکلک عصبانی برای سرگرمی است.
-
-
return e
: این تابع در صورتی که موردی گیرافتاده بود، استثنا را برمیگرداند، یا اگر ورود موفقیت آمیز بود، صفر را برمیگرداند.
به طور خلاصه، این کد راهی برای مدیریت ورود به سیستم کاربر با استفاده از Credential Manager API، مدیریت عملیات ناهمزمان، مدیریت خطاهای احتمالی، و ارائه بازخورد به کاربر از طریق تستها و گزارشها ارائه میکند و در عین حال طنزی به مدیریت خطا اضافه میکند.
جریان صفحه پایین را در برنامه پیاده سازی کنید
اکنون میتوانیم با استفاده از کد زیر و Web Application Client ID که قبلاً از Google Cloud Console کپی کردهایم، تماسی برای راهاندازی جریان BottomSheet در کلاس MainActivity
خود راهاندازی کنیم:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
//This will trigger on launch
BottomSheet(webClientId)
}
}
}
}
}
اکنون میتوانیم پروژه خود را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:
- دکمه run را فشار دهید:
- هنگامی که برنامه شما روی شبیه ساز اجرا می شود، باید صفحه پایین ورود به سیستم را مشاهده کنید. ادامه دهید و روی Continue کلیک کنید تا ورود به سیستم را آزمایش کنید
- باید پیام Toast را ببینید که نشان می دهد ورود با موفقیت انجام شد!
7. جریان دکمه
Button Flow for Sign in with Google، ثبت نام یا ورود کاربران به برنامه Android خود را با استفاده از حساب Google موجود آسانتر میکند. اگر برگه پایینی را رد کنند یا فقط ترجیح دهند صریحاً از حساب Google خود برای ورود به سیستم یا ثبت نام استفاده کنند، به آن ضربه می زنند. برای توسعه دهندگان، این به معنای نصب نرم تر و اصطکاک کمتر در هنگام ثبت نام است.
در حالی که این کار را میتوان با یک دکمه Jetpack Compose خارج از جعبه انجام داد، ما از یک نماد نام تجاری از پیش تأیید شده از صفحه دستورالعملهای نام تجاری ورود به سیستم با Google استفاده میکنیم.
نماد نام تجاری را به پروژه اضافه کنید
- ZIP نمادهای برند از پیش تأیید شده را از اینجا دانلود کنید
- signin-assest.zip را از دانلودهای خود خارج کنید (این میزان بسته به سیستم عامل رایانه شما متفاوت است). اکنون می توانید پوشه signin-assets را باز کرده و نمادهای موجود را بررسی کنید. برای این Codelab،
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png
استفاده خواهیم کرد. - فایل را کپی کنید
- با کلیک راست بر روی پوشه drawable و کلیک کردن روی Paste ، در Android Studio زیر res > drawable را در پروژه جایگذاری کنید (ممکن است لازم باشد پوشه res را باز کنید تا آن را ببینید)
- یک گفتگو نشان داده می شود که از شما می خواهد نام فایل را تغییر دهید و دایرکتوری که به آن اضافه می شود را تأیید کنید. نام دارایی را به siwg_button.png تغییر دهید سپس روی OK کلیک کنید
کد جریان دکمه
این کد از همان تابع signIn()
استفاده میکند که برای BottomSheet()
استفاده میشود، اما از GetSignInWithGoogleOption
به جای GetGoogleIdOption
استفاده میکند زیرا این جریان از اعتبارنامهها و کلیدهای عبور ذخیره شده در دستگاه برای نمایش گزینههای ورود استفاده نمیکند. در اینجا کدی است که می توانید در زیر تابع BottomSheet()
قرار دهید:
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
برای تفکیک کاری که کد انجام می دهد:
fun ButtonUI(webClientId: String)
: تابعی به نام ButtonUI
را اعلام می کند که webClientId
(شناسه مشتری پروژه Google Cloud شما) را به عنوان آرگومان می پذیرد.
val context = LocalContext.current
: زمینه Android فعلی را بازیابی می کند. این برای عملیات های مختلف، از جمله راه اندازی مؤلفه های UI مورد نیاز است.
val coroutineScope = rememberCoroutineScope()
: یک محدوده coroutine ایجاد می کند. این برای مدیریت وظایف ناهمزمان استفاده می شود و به کد اجازه می دهد بدون مسدود کردن رشته اصلی اجرا شود. rememberCoroutineScope
() یک تابع قابل ترکیب از Jetpack Compose است که محدودهای را ارائه میکند که با چرخه عمر composable مرتبط است.
val onClick: () -> Unit = { ... }
: این تابع یک تابع lambda ایجاد می کند که با کلیک روی دکمه اجرا می شود. تابع لامبدا:
-
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build()
: این قسمت یک شیGetSignInWithGoogleOption
ایجاد می کند. این شیء برای تعیین پارامترهای فرآیند "ورود به سیستم با Google" استفاده می شود، بهwebClientId
و یک nonce (رشته تصادفی که برای امنیت استفاده می شود) نیاز دارد. -
val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build()
: این یک شیGetCredentialRequest
می سازد. این درخواست برای دریافت اعتبار کاربری با استفاده از Credential Manager استفاده خواهد شد.GetCredentialRequest
GetSignInWithGoogleOption
ایجاد شده قبلی را به عنوان یک گزینه اضافه می کند تا اعتبارنامه "ورود به سیستم با Google" را درخواست کند.
-
coroutineScope.launch { ... }
: یکCoroutineScope
برای مدیریت عملیات ناهمزمان (با استفاده از کوروتین).-
signIn(request, context)
: تابعsignIn
() از قبل تعریف شده ما را فراخوانی می کند
-
Image(...)
: این یک تصویر را با استفاده از painterResource
که تصویر R.drawable.siwg_button
بارگیری می کند، ارائه می کند.
-
Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick)
:-
fillMaxSize()
: باعث می شود تصویر فضای موجود را پر کند. -
clickable(enabled = true, onClick = onClick)
: تصویر را قابل کلیک می کند و پس از کلیک کردن، تابع onClick lambda را که قبلاً تعریف شده بود، اجرا می کند.
-
به طور خلاصه، این کد یک دکمه «ورود با Google» را در یک رابط کاربری Jetpack Compose تنظیم میکند. وقتی روی دکمه کلیک میشود، یک درخواست اعتبار برای راهاندازی Credential Manager آماده میکند و به کاربر اجازه میدهد با حساب Google خود وارد شود.
اکنون باید کلاس MainActivity را برای اجرای تابع ButtonUI()
به روز کنیم:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
اکنون میتوانیم پروژه خود را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:
- دکمه run را فشار دهید:
- هنگامی که برنامه ها روی شبیه ساز اجرا می شوند، BottomSheet باید نمایش داده شود. خارج از آن کلیک کنید تا ببندید.
- اکنون باید دکمه ای را که ایجاد کردیم در برنامه نمایش داده شود ببینید. پیش بروید و روی آن کلیک کنید تا گفتگوی ورود به سیستم را ببینید
- برای ورود به حساب کاربری خود کلیک کنید!
8. نتیجه گیری
شما این کد لبه را تمام کردید! برای اطلاعات بیشتر یا راهنمایی در مورد ورود به سیستم با Google در Android، بخش سؤالات متداول زیر را ببینید:
سوالات متداول
- Stackoverflow
- راهنمای عیب یابی مدیر اعتبار اندروید
- سوالات متداول مدیریت اعتبار اندروید
- مرکز راهنمایی تأیید برنامه OAuth
کد کامل MainActivity.kt
در اینجا کد کامل MainActivity.kt برای مرجع است:
package com.example.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.example.ui.theme.ExampleTheme
import android.content.ContentValues.TAG
import android.content.Context
import android.credentials.GetCredentialException
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialCustomException
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.GetCredentialRequest
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import java.security.SecureRandom
import java.util.Base64
import kotlinx.coroutines.CoroutineScope
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
signIn(requestFalse, context)
}
}
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
signIn(coroutineScope, request, context)
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}