כניסה לחשבון מקושר עבור Android

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

האפשרות 'כניסה לחשבון מקושר' זמינה כחלק מתהליך הכניסה בהקשה אחת ל-Android. כלומר, אין צורך לייבא ספרייה נפרדת אם התכונה One Tap כבר מופעלת באפליקציה.

במסמך הזה נסביר איך לשנות את האפליקציה ל-Android כך שתתמוך ב'כניסה לחשבון מקושר'.

איך זה עובד

  1. הבעת הסכמה להצגת חשבונות מקושרים בתהליך הכניסה בהקשה אחת.
  2. אם המשתמש נכנס לחשבון Google וקישר את חשבון Google שלו לחשבון שלו בשירות שלכם, יוחזר אסימון מזהה לחשבון המקושר.
  3. מוצגת למשתמש הודעת כניסה בהקשה אחת עם אפשרות להיכנס לשירות שלכם באמצעות החשבון המקושר שלו.
  4. אם המשתמש בוחר להמשיך עם החשבון המקושר, האסימון המזהה של המשתמש מוחזר לאפליקציה. צריך להתאים אותו לאסימון שנשלח לשרת שלכם בשלב 2 כדי לזהות את המשתמש המחובר.

הגדרה

הגדרת סביבת הפיתוח

אפשר לקבל את הגרסה העדכנית ביותר של Google Play Services אצל מארח הפיתוח:

  1. פותחים את מנהל ה-SDK של Android.
  1. בקטע SDK Tools, מאתרים את Google Play Services.

  2. אם הסטטוס של החבילות האלה אינו Installed, בוחרים את שתיהן ולוחצים על Install Packages (התקנת חבילות).

הגדרת האפליקציה

  1. בקובץ build.gradle ברמת הפרויקט, כוללים את מאגר Maven של Google בקטע buildscript וגם בקטע allprojects.

    buildscript {
        repositories {
            google()
        }
    }
    
    allprojects {
        repositories {
            google()
        }
    }
    
  2. מוסיפים את יחסי התלות של ה-API "Link with Google" לקובץ gradle ברמת האפליקציה של המודול, בדרך כלל app/build.gradle:

    dependencies {
      implementation 'com.google.android.gms:play-services-auth:21.2.0'
    }
    

שינוי האפליקציה ל-Android כך שתתמוך בכניסה לחשבון מקושר

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

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

  1. צריך ליצור פעילות כדי לקבל את התוצאה של כוונת הכניסה

    Kotlin

      private val activityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
          try {
            val signInCredentials = Identity.signInClient(this)
                                    .signInCredentialFromIntent(result.data)
            // Review the Verify the integrity of the ID token section for
            // details on how to verify the ID token
            verifyIdToken(signInCredential.googleIdToken)
          } catch (e: ApiException) {
            Log.e(TAG, "Sign-in failed with error code:", e)
          }
        } else {
          Log.e(TAG, "Sign-in failed")
        }
      }
    

    Java

      private final ActivityResultLauncher<IntentSenderResult>
        activityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        result -> {
        If (result.getResultCode() == RESULT_OK) {
            try {
              SignInCredential signInCredential = Identity.getSignInClient(this)
                             .getSignInCredentialFromIntent(result.getData());
              verifyIdToken(signInCredential.getGoogleIdToken());
            } catch (e: ApiException ) {
              Log.e(TAG, "Sign-in failed with error:", e)
            }
        } else {
            Log.e(TAG, "Sign-in failed")
        }
    });
    
  2. יצירה של בקשת הכניסה

    Kotlin

    private val tokenRequestOptions =
    GoogleIdTokenRequestOptions.Builder()
      .supported(true)
      // Your server's client ID, not your Android client ID.
      .serverClientId(getString("your-server-client-id")
      .filterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                               scopes)
      .build()
    

    Java

     private final GoogleIdTokenRequestOptions tokenRequestOptions =
         GoogleIdTokenRequestOptions.Builder()
      .setSupported(true)
      .setServerClientId("your-service-client-id")
      .setFilterByAuthorizedAccounts(true)
      .associateLinkedAccounts("service-id-of-and-defined-by-developer",
                                scopes)
      .build()
    
  3. הפעלה של Intent בהמתנה לכניסה

    Kotlin

     Identity.signInClient(this)
        .beginSignIn(
      BeginSignInRequest.Builder()
        .googleIdTokenRequestOptions(tokenRequestOptions)
      .build())
        .addOnSuccessListener{result ->
          activityResultLauncher.launch(result.pendingIntent.intentSender)
      }
      .addOnFailureListener {e ->
        Log.e(TAG, "Sign-in failed because:", e)
      }
    

    Java

     Identity.getSignInClient(this)
      .beginSignIn(
        BeginSignInRequest.Builder()
          .setGoogleIdTokenRequestOptions(tokenRequestOptions)
          .build())
      .addOnSuccessListener(result -> {
        activityResultLauncher.launch(
            result.getPendingIntent().getIntentSender());
    })
    .addOnFailureListener(e -> {
      Log.e(TAG, "Sign-in failed because:", e);
    });
    

אימות התקינות של האסימון המזהה

כדי לוודא שהאסימון תקין, צריך לוודא שהקריטריונים הבאים מתקיימים:

  • Google חתומה באופן תקין על האסימון המזהה. יש להשתמש במפתחות הציבוריים של Google (זמינים בפורמט JWK או PEM) לאימות החתימה של האסימון. המפתחות האלה עוברים רוטציה באופן קבוע. כדאי לבדוק את הכותרת Cache-Control בתשובה כדי לקבוע מתי צריך לאחזר אותם שוב.
  • הערך של aud באסימון המזהה שווה לאחד ממזהי הלקוח של האפליקציה שלך. הבדיקה הזו נדרשת כדי למנוע אסימונים מזהים שהונפקו לאפליקציה זדונית שנעשה בה שימוש כדי לגשת לנתונים של אותו משתמש בשרת הקצה העורפי של האפליקציה.
  • הערך של iss באסימון המזהה שווה ל-accounts.google.com או ל-https://accounts.google.com.
  • מועד התפוגה (exp) של האסימון המזהה לא חלף.
  • כדי לוודא שהאסימון המזהה מייצג חשבון ארגוני ב-Google Workspace או ב-Cloud, אפשר לבדוק את ההצהרה hd, שבה מצוין הדומיין המתארח של המשתמש. צריך להשתמש באפשרות הזו כשמגבילים את הגישה למשאב רק לחברים בדומיינים מסוימים. אם לא ההצהרה הזו לא תשויך, החשבון לא שייך לדומיין באירוח של Google.

במקום לכתוב קוד משלך לביצוע שלבי האימות האלה, מומלץ מאוד להשתמש בספריית לקוח של Google API לפלטפורמה שלך, או בספריית JWT לשימוש כללי. לפיתוח ולניפוי באגים, אפשר לקרוא לנקודת הקצה של האימות tokeninfo.

שימוש בספריית לקוח של Google API

השימוש בספריית הלקוח של Java Google API הוא הדרך המומלצת לאמת אסימונים מזהים של Google בסביבת הייצור.

Java

  import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
  import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
  import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;

  ...

  GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
      // Specify the CLIENT_ID of the app that accesses the backend:
      .setAudience(Collections.singletonList(CLIENT_ID))
      // Or, if multiple clients access the backend:
      //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
      .build();

  // (Receive idTokenString by HTTPS POST)

  GoogleIdToken idToken = verifier.verify(idTokenString);
  if (idToken != null) {
    Payload payload = idToken.getPayload();

    // Print user identifier
    String userId = payload.getSubject();
    System.out.println("User ID: " + userId);

    // Get profile information from payload
    String email = payload.getEmail();
    boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
    String name = (String) payload.get("name");
    String pictureUrl = (String) payload.get("picture");
    String locale = (String) payload.get("locale");
    String familyName = (String) payload.get("family_name");
    String givenName = (String) payload.get("given_name");

    // Use or store profile information
    // ...

  } else {
    System.out.println("Invalid ID token.");
  }

ה-method GoogleIdTokenVerifier.verify() מאמתת את חתימת ה-JWT, את ההצהרה aud, את ההצהרה iss ואת ההצהרהexp.

אם אתם צריכים לוודא שהאסימון המזהה מייצג חשבון ארגוני ב-Google Workspace או ב-Cloud, תוכלו לאמת את ההצהרה hd על ידי בדיקת שם הדומיין שהוחזר באמצעות השיטה Payload.getHostedDomain().

מתבצעת קריאה לנקודת הקצה של פרטי האסימון

דרך קלה לאמת חתימה של אסימון מזהה לצורך ניפוי באגים היא להשתמש בנקודת הקצה tokeninfo. הפעלה של נקודת הקצה הזו כרוכה בבקשת רשת נוספת שמבצעת את רוב תהליך האימות בזמן הבדיקה של האימות והחילוץ של המטען הייעודי (payload) בקוד שלך. היא לא מתאימה לשימוש בקוד של סביבת הייצור כי הבקשות עשויות להיות מווסתות או שעשויות לכלול שגיאות מדי פעם.

כדי לאמת אסימון מזהה באמצעות נקודת הקצה של tokeninfo, יש לשלוח בקשת HTTPS POST או GET לנקודת הקצה, ולהעביר את האסימון המזהה בפרמטר id_token. לדוגמה, כדי לאמת את האסימון "XYZ123", יש לשלוח את בקשת GET הבאה:

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

אם האסימון נחתם כראוי וההצהרות של iss ו-exp כוללות את הערכים הצפויים, תתקבל תגובת HTTP 200, שבה הגוף מכיל את ההצהרות על אסימונים בפורמט JSON. זוהי דוגמה לתשובה:

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

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