הטמעה של 'כניסה באמצעות חשבון Google'

במדריך הזה מוסבר איך להטמיע את התכונה "כניסה באמצעות חשבון Google" ומתוארים השלבים הבאים:

  • מוסיפים יחסי תלות לאפליקציה.
  • הפעלה של CredentialManager.
  • יוצרים את תהליך הגיליון התחתון.
  • יוצרים את תהליך הכפתור.
  • מטפלים בתגובה לכניסה.
  • טיפול בשגיאות.
  • טיפול ביציאה מהחשבון.

הוספת יחסי תלות לאפליקציה

בקובץ build.gradle של המודול, מצהירים על יחסי תלות באמצעות הגרסה העדכנית של Credential Manager,‏ Play Services Auth ו-googleid:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
    implementation("com.google.android.libraries.identity.googleid:googleid:<latest version>")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
    implementation "com.google.android.libraries.identity.googleid:googleid:<latest version>"
}

הפעלת Credential Manager

משתמשים בהקשר של האפליקציה או הפעילות כדי ליצור אובייקט CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

יצירת תהליך הגיליון התחתון

הגיליון התחתון הוא ממשק המשתמש המובנה של Credential Manager. השימוש בממשק המשתמש הזה יוצר חוויה עקבית בכל שיטות האימות, כמו סיסמאות, מפתחות גישה וכניסה באמצעות חשבון Google.

הגדרת בקשת הכניסה לחשבונות שכבר אושרו

מנסים לשלוח בקשת כניסה באמצעות חשבון Google באמצעות GetGoogleIdOption כדי לאחזר את טוקן המזהה של המשתמש ב-Google.

קטעי הקוד הבאים בודקים אם החשבון הוא חשבון מורשה.

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(true)
    .setServerClientId(WEB_CLIENT_ID)
    .setAutoSelectEnabled(true)
    .setNonce(generateSecureRandomNonce())
    .build()

האובייקט googleIdOption של הבקשה מוגדר באופן הבא:

  • סינון חשבונות שאושרו בעבר: כדי לאחזר את החשבונות המורשים ששימשו בעבר לכניסה לאפליקציה, מגדירים את setFilterByAuthorizedAccounts לערך true.

    שימו לב שערך ברירת המחדל של setFilterByAuthorizedAccounts הוא true, כלומר פעולת ברירת המחדל של ממשק המשתמש של הגיליון התחתון היא להציג רק חשבונות שאושרו בעבר.

  • מגדירים את מזהה הלקוח בשרת: מגדירים את הפרמטר setServerClientId. ‫webClientId הוא מזהה לקוח האינטרנט שהגדרתם ל-OAuth בפרויקט ב-Google Cloud כשהשלמתם את הדרישות המוקדמות.

  • הפעלת כניסה אוטומטית (אופציונלי): כדי להפעיל כניסה אוטומטית למשתמשים חוזרים, משתמשים ב-setAutoSelectEnabled(true) וב-setFilterByAuthorizedAccounts(true). כך, משתמשים שכבר נכנסו לחשבון בעבר לא יצטרכו לעשות את זה שוב.

    כניסה אוטומטית אפשרית רק אם מתקיימים התנאים הבאים:

    • במכשיר יש רק חשבון מורשה אחד, והחשבון הזה שימש בעבר כדי להיכנס לאפליקציה במכשיר. אם יש כמה חשבונות מורשים במכשיר, הכניסה האוטומטית מושבתת.
    • המשתמש לא יצא מהאפליקציה באופן מפורש במהלך הסשן הקודם.
    • המשתמש לא השבית את הכניסה האוטומטית בהגדרות של חשבון Google.
  • הגדרת צופן חד-פעמי (אופציונלי): כדי להפעיל אבטחה משופרת, מגדירים צופן חד-פעמי לאימות בצד השרת. כדי למנוע התקפות של הפעלה חוזרת, אפשר לכלול צופן חד-פעמי (nonce) לאימות בצד השרת באמצעות setNonce(). חשוב לוודא שהקוד בצד השרת מאמת שהמספרים החד-פעמיים של הבקשה והתגובה זהים.

    כדי ליצור את הצופן החד-פעמי (nonce), השתמשו בפונקציה שדומה לפונקציה הבאה, שיוצרת צופן חד-פעמי (nonce) אקראי חזק מבחינה קריפטוגרפית באורך שצוין ומקודדת אותו באמצעות Base64:

fun generateSecureRandomNonce(byteLength: Int = 32): String {
    val randomBytes = ByteArray(byteLength)
    SecureRandom().nextBytes(randomBytes)
    return Base64.encodeToString(randomBytes, Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING)
}

בקשת כניסה לחשבון

כדי לבדוק אם למשתמש יש חשבון מורשה במכשיר, מפעילים את ה-method‏ getCredential:

val request: GetCredentialRequest = GetCredentialRequest.Builder()
    .addCredentialOption(googleIdOption)
    .build()

// Use an activity-based context to avoid undefined system UI
// launching behavior.
val mutableContext = MutableContextWrapper(activityContext)
coroutineScope {
    try {
        val result = credentialManager.getCredential(
            request = request,            
            context = mutableContext // Use MutableContextWrapper to avoid memory leak during configuration changes
        )
        handleSignIn(result)
    } catch (e: GetCredentialException) {
        // Handle failures
    }
}

הגדרת בקשת הכניסה אם אין חשבונות מורשים זמינים

אם אין משתמשים מורשים לאפליקציה במכשיר, CredentialManager מחזיר NoCredentialException. במקרה כזה, צריך להשבית את המסנן של החשבונות המורשים כדי שהמשתמש יוכל להשתמש בחשבון אחר כדי להירשם.

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(false)
    .setServerClientId(WEB_CLIENT_ID)
    .setNonce(generateSecureRandomNonce())
    .build()

בשלב הבא, מבקשים כניסה בדומה לאופן שבו עשיתם זאת עבור חשבונות מורשים.

יצירת תהליך הכפתור

כדאי להשתמש בכפתור אם אתם רוצים שהמשתמשים יוכלו להיכנס באמצעות חשבון Google בתנאים הבאים:

  • המשתמש סגר את ממשק המשתמש של הגיליון התחתון של Credential Manager.
  • אין חשבונות Google במכשיר.
  • צריך לאמת מחדש את החשבונות הקיימים במכשיר.

יצירת ממשק המשתמש של הכפתור

אפשר לעשות את זה באמצעות כפתור של Jetpack Compose, אבל אתם יכולים להשתמש בסמל מותג שאושר מראש מהדף הנחיות המיתוג של כניסה באמצעות חשבון Google.

יצירת תהליך הכניסה לחשבון

יוצרים בקשת כניסה באמצעות חשבון Google עם GetSignInWithGoogleOption כדי לאחזר טוקן של מזהה Google.

val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(
    serverClientId = WEB_CLIENT_ID
).setNonce(generateSecureRandomNonce())
    .build()

בשלב הבא, מבקשים כניסה באופן דומה למה שעשיתם בממשק המשתמש של הגיליון התחתון.

יוצרים את פונקציית הכניסה המשותפת לגיליון התחתון ולכפתור

כדי לטפל בכניסה, מבצעים את השלבים הבאים:

  1. משתמשים בפונקציה getCredential() של CredentialManager. אם התגובה תקינה, מחלצים את CustomCredential, שצריך להיות מסוג GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.
  2. ממירים את האובייקט ל-GoogleIdTokenCredential באמצעות השיטה GoogleIdTokenCredential.createFrom().

  3. מאמתים את פרטי הכניסה בשרת של הגורם המסתמך.

  4. חשוב לוודא שאתם מטפלים בשגיאות בצורה מתאימה.

fun handleSign(result: GetCredentialResponse) {
    // Handle the successfully returned credential.
    val credential = result.credential

    when (credential) {
        is CustomCredential -> {
            if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
                try {
                    // Use googleIdTokenCredential and extract the ID for server-side validation.
                    val googleIdTokenCredential = GoogleIdTokenCredential
                        .createFrom(credential.data)
                } catch (e: GoogleIdTokenParsingException) {
                    Log.e(TAG, "Received an invalid google id token response", e)
                }
            } else {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }

        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential")
        }
    }
}

טיפול בשגיאות

כדאי לעיין בשגיאות שמפורטות בקטע פתרון בעיות כדי לוודא שהקוד שלכם מטפל בכל תרחישי השגיאות האפשריים.

טיפול ביציאה מהחשבון

חשוב לספק למשתמשים מנגנון ליציאה מהאפליקציה. לדוגמה, יכול להיות שלמשתמש יש כמה חשבונות Google במכשיר והוא רוצה להיכנס לחשבון אחר. אפשר להוסיף את הקישור הזה, למשל, בדף ההגדרות.

ספק פרטי הכניסה יכול לאחסן סשן פעיל של פרטי כניסה ולהשתמש בו כדי להגביל את אפשרויות הכניסה לבקשות כניסה עתידיות. לדוגמה, הוא יכול לתת עדיפות לפרטי הכניסה הפעילים על פני כל פרטי כניסה זמינים אחרים.

כשמשתמש יוצא מהאפליקציה, צריך להפעיל את ה-method‏ clearCredentialState() של ה-API כדי למחוק את המצב הנוכחי של פרטי הכניסה של המשתמש מכל ספקי פרטי הכניסה. הפעולה הזו תיידע את כל ספקי פרטי הכניסה שצריך לנקות את כל הסשנים של פרטי הכניסה ששמורים באפליקציה הנתונה, ותספק למשתמשים אפשרויות כניסה מלאות בפעם הבאה.