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

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

תהליך הכניסה לקבלת אסימון גישה לאפליקציה בצד השרת מתואר בהמשך.

לקודים חד-פעמיים יש כמה יתרונות אבטחה. באמצעות הקודים, Google מספקת אסימונים ישירות לשרת ללא מתווכים. אנחנו לא ממליצים על דליפת קודים, אבל קשה מאוד להשתמש בהם ללא סוד הלקוח. שמור על סוד הלקוח!

הטמעת התהליך החד-פעמי

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

הקוד לדוגמה הבא מדגים איך לבצע תהליך של קוד חד-פעמי.

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

שלב 1: יוצרים מזהה לקוח וסוד לקוח

כדי ליצור מזהה לקוח וסוד לקוח, יוצרים פרויקט במסוף Google API, מגדירים מזהה לקוח ב-OAuth ורושמים את מקורות ה-JavaScript:

  1. נכנסים אל Google API Console.

  2. בוחרים פרויקט קיים מהרשימה הנפתחת של הפרויקט או לוחצים על Create a new project כדי ליצור פרויקט חדש.

  3. בסרגל הצד, בקטע API & Services, בוחרים באפשרות Credentials ולוחצים על Configure consent screen (הגדרת מסך הסכמה).

    בוחרים כתובת אימייל, מציינים שם מוצר ומקישים על שמירה.

  4. בכרטיסייה Credentials בחר ברשימה הנפתחת Create credentials ובחר באפשרות OAuth client ID.

  5. בקטע Application type (סוג האפליקציה), בוחרים באפשרות Web application.

    רושמים את המקורות שמהם האפליקציה מורשית לגשת ל-Google APIs באופן הבא. מקור הוא שילוב ייחודי של פרוטוקול, שם מארח ויציאה.

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

      http://localhost:8080
      https://myproductionurl.example.com
      
    2. השדה URI של הפניה אוטומטית מורשית לא מחייב ערך. לא נעשה שימוש במזהי URI להפניה אוטומטית עם ממשקי API של JavaScript.

    3. לוחצים על הלחצן יצירה.

  6. מתיבת הדו-שיח לקוח OAuth שמתקבלת, מעתיקים את מזהה הלקוח. מזהה הלקוח מאפשר לאפליקציה לגשת ל-Google APIs שהופעלו.

שלב 2: כוללים את ספריית הפלטפורמה של Google בדף

צריך לכלול את הסקריפטים הבאים שמדגימים פונקציה אנונימית שמכניסה סקריפט ל-DOM של דף האינטרנט הזה של index.html.

<!-- The top of file index.html -->
<html itemscope itemtype="http://schema.org/Article">
<head>
  <!-- BEGIN Pre-requisites -->
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
  </script>
  <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer>
  </script>
  <!-- END Pre-requisites -->

שלב 3: אתחול האובייקט GoogleAuth

טוענים את ספריית auth2 וקוראים ל-gapi.auth2.init() כדי לאתחל את האובייקט GoogleAuth. מציינים את מזהה הלקוח ואת היקפי ההרשאות שרוצים לבקש כשמתקשרים ל-init().

<!-- Continuing the <head> section -->
  <script>
    function start() {
      gapi.load('auth2', function() {
        auth2 = gapi.auth2.init({
          client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
          // Scopes to request in addition to 'profile' and 'email'
          //scope: 'additional_scope'
        });
      });
    }
  </script>
</head>
<body>
  <!-- ... -->
</body>
</html>

שלב 4: הוספה של לחצן הכניסה לדף

מוסיפים את לחצן הכניסה לדף האינטרנט ומצרפים handler של קליקים להפעלת grantOfflineAccess() כדי להתחיל את התהליך עם קוד חד-פעמי.

<!-- Add where you want your sign-in button to render -->
<!-- Use an image that follows the branding guidelines in a real app -->
<button id="signinButton">Sign in with Google</button>
<script>
  $('#signinButton').click(function() {
    // signInCallback defined in step 6.
    auth2.grantOfflineAccess().then(signInCallback);
  });
</script>

שלב 5: מזינים את פרטי הכניסה של המשתמש

המשתמש לוחץ על לחצן הכניסה ומעניק לאפליקציה גישה להרשאות שביקשת. לאחר מכן, פונקציית הקריאה החוזרת שציינתם בשיטה grantOfflineAccess().then() מועברת אובייקט JSON עם קוד הרשאה. למשל:

{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}

שלב 6: שולחים את קוד ההרשאה לשרת

code הוא הקוד החד-פעמי שהשרת יכול להחליף באסימון גישה ובאסימון רענון משלו. אפשר לקבל אסימון רענון רק אחרי שלמשתמש מוצגת תיבת דו-שיח לאישור שמבקשת גישה במצב אופליין. אם ציינתם את select-account prompt ב-OfflineAccessOptions בשלב 4, עליכם לאחסן את אסימון הרענון שמאחזרים לשימוש מאוחר יותר, כי ההחלפות הבאות יחזירו null עבור אסימון הרענון. התהליך הזה מספק אבטחה מוגברת בתהליך OAuth 2.0 הרגיל.

אסימוני הגישה תמיד מוחזרים עם קוד הרשאה תקף.

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

<!-- Last part of BODY element in file index.html -->
<script>
function signInCallback(authResult) {
  if (authResult['code']) {

    // Hide the sign-in button now that the user is authorized, for example:
    $('#signinButton').attr('style', 'display: none');

    // Send the code to the server
    $.ajax({
      type: 'POST',
      url: 'http://example.com/storeauthcode',
      // Always include an `X-Requested-With` header in every AJAX request,
      // to protect against CSRF attacks.
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      },
      contentType: 'application/octet-stream; charset=utf-8',
      success: function(result) {
        // Handle or verify the server response.
      },
      processData: false,
      data: authResult['code']
    });
  } else {
    // There was an error.
  }
}
</script>

שלב 7: מחליפים את קוד ההרשאה באסימון גישה

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

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

למשל:

Java
// (Receive authCode via HTTPS POST)


if (request.getHeader("X-Requested-With") == null) {
  // Without the `X-Requested-With` header, this request could be forged. Aborts.
}

// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.cloud.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/path/to/client_secret.json";

// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
    GoogleClientSecrets.load(
        JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
          new GoogleAuthorizationCodeTokenRequest(
              new NetHttpTransport(),
              JacksonFactory.getDefaultInstance(),
              "https://oauth2.googleapis.com/token",
              clientSecrets.getDetails().getClientId(),
              clientSecrets.getDetails().getClientSecret(),
              authCode,
              REDIRECT_URI)  // Specify the same redirect URI that you use with your web
                             // app. If you don't have a web version of your app, you can
                             // specify an empty string.
              .execute();

String accessToken = tokenResponse.getAccessToken();

// Use access token to call API
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Drive drive =
    new Drive.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
        .setApplicationName("Auth Code Exchange Demo")
        .build();
File file = drive.files().get("appfolder").execute();

// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject();  // Use this value as a key to identify a user.
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");
Python
from apiclient import discovery
import httplib2
from oauth2client import client

# (Receive auth_code by HTTPS POST)


# If this request does not have `X-Requested-With` header, this could be a CSRF
if not request.headers.get('X-Requested-With'):
    abort(403)

# Set path to the Web application client_secret_*.json file you downloaded from the
# Google API Console: https://console.cloud.google.com/apis/credentials
CLIENT_SECRET_FILE = '/path/to/client_secret.json'

# Exchange auth code for access token, refresh token, and ID token
credentials = client.credentials_from_clientsecrets_and_code(
    CLIENT_SECRET_FILE,
    ['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
    auth_code)

# Call Google API
http_auth = credentials.authorize(httplib2.Http())
drive_service = discovery.build('drive', 'v3', http=http_auth)
appfolder = drive_service.files().get(fileId='appfolder').execute()

# Get profile info from ID token
userid = credentials.id_token['sub']
email = credentials.id_token['email']