כניסה באמצעות חשבון 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 (APIs & 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. לוחצים על הלחצן Create.

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

שלב 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: נכנסים לחשבון משתמש

המשתמש לוחץ על לחצן הכניסה ומעניק לאפליקציה גישה להרשאות שביקשת. לאחר מכן, פונקציית הקריאה החוזרת שציינתם ב-method 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']