מה עושים בכניסות חוזרות

זוהי ההדרכה השלישית בסדרת ההדרכה המפורטת על תוספים ל-Classroom.

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

במהלך ההדרכה המפורטת תשלימו את הפעולות הבאות:

  • הטמעת אחסון קבוע לפרטי הכניסה של המשתמשים שלנו.
  • מאחזרים ומעריכים את הפרמטרים הבאים של השאילתה:
    • login_hint: מספר Google ID של המשתמש המחובר.
    • hd: הדומיין של המשתמש המחובר.

חשוב לשים לב שרק אחד מהפרטים האלה נשלח. ה-API של Classroom שולח את הפרמטר hd אם המשתמש עוד לא אישר את האפליקציה. אחרת, ה-API שולח את login_hint. הרשימה המלאה של הפרמטרים של שאילתות זמינה בדף המדריך בנושא iframes.

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

הסבר על פרמטרים של שאילתות iframe

מערכת Classroom טוענת את ה-URI להגדרת קבצים מצורפים של התוסף ברגע הפתיחה. מערכת Classroom מצרפת ל-URI כמה פרמטרים של שאילתה מסוג GET. הפרמטרים האלה מכילים מידע שימושי לפי ההקשר. לדוגמה, אם ה-URI של הקבצים המצורפים הוא https://example.com/addon, מערכת Classroom יוצרת את ה-iframe כאשר כתובת ה-URL של המקור מוגדרת ל-https://example.com/addon?courseId=XXX&postId=YYY&addOnToken=ZZZ, כאשר XXX, YYY ו-ZZZ הם מזהי המחרוזת. לתיאור מפורט של התרחיש הזה תוכלו לעיין במדריך לשימוש ב-iframes.

יש חמישה פרמטרים אפשריים של שאילתה בכתובת ה-URL לגילוי:

  • courseId: מזהה הקורס הנוכחי ב-Classroom.
  • postId: המזהה של פוסט המטלה שהמשתמש עורך או יוצר.
  • addOnToken: אסימון שמשמש למתן הרשאה לפעולות מסוימות בתוסף Classroom.
  • login_hint: מזהה Google של המשתמש הנוכחי.
  • hd: הדומיין המארח של המשתמש הנוכחי, כמו example.com.

ההדרכה המפורטת הזו כתובת hd ו-login_hint. המשתמשים מנותבים בהתאם לפרמטר השאילתה שסופק, לתהליך ההרשאה אם hd, או לדף הגילוי של התוסף, אם login_hint.

גישה לפרמטרים של שאילתות

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

Python

עוברים להגדרות של הנתיבים ב-Flask (routes.py אם פועלים לפי הדוגמה שלנו). בחלק העליון של מסלול הנחיתה של התוסף (/classroom-addon בדוגמה שסיפקנו), מאחזרים ושומרים את הפרמטרים login_hint ו-hd של השאילתה:

# Retrieve the login_hint and hd query parameters.
login_hint = flask.request.args.get("login_hint")
hd = flask.request.args.get("hd")

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

# It's possible that we might return to this route later, in which case the
# parameters will not be passed in. Instead, use the values cached in the
# session.

# If neither query parameter is available, use the values in the session.
if login_hint is None and hd is None:
    login_hint = flask.session.get("login_hint")
    hd = flask.session.get("hd")

# If there's no login_hint query parameter, then check for hd.
# Send the user to the sign in page.
elif hd is not None:
    flask.session["hd"] = hd
    return start_auth_flow()

# If the login_hint query parameter is available, we'll store it in the
# session.
else:
    flask.session["login_hint"] = login_hint

Java

מנווטים אל מסלול הנחיתה של התוסף במחלקה של הבקר (/addon-discovery בדוגמה AuthController.java שבדוגמה). בתחילת המסלול הזה, מאחזרים ושומרים את הפרמטרים של השאילתה login_hint ו-hd.

/** Retrieve the login_hint or hd query parameters from the request URL. */
String login_hint = request.getParameter("login_hint");
String hd = request.getParameter("hd");

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

/** If neither query parameter is sent, use the values in the session. */
if (login_hint == null && hd == null) {
    login_hint = (String) session.getAttribute("login_hint");
    hd = (String) session.getAttribute("hd");
}

/** If the hd query parameter is provided, add hd to the session and route
*   the user to the authorization page. */
else if (hd != null) {
    session.setAttribute("hd", hd);
    return startAuthFlow(model);
}

/** If the login_hint query parameter is provided, add it to the session. */
else if (login_hint != null) {
    session.setAttribute("login_hint", login_hint);
}

הוספת הפרמטרים של השאילתה לתהליך ההרשאה

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

Python

עוברים אל נתיב ההרשאה בקובץ של שרת Flask (/authorizeבדוגמה שלנו). מוסיפים את הארגומנטים login_hint ו-hd לקריאה ל-flow.authorization_url.

authorization_url, state = flow.authorization_url(
    # Enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type="offline",
    # Enable incremental authorization. Recommended as a best practice.
    include_granted_scopes="true",
    # The user will automatically be selected if we have the login_hint.
    login_hint=flask.session.get("login_hint"),
    # If we don't have login_hint, passing hd will reduce the list of
    # accounts in the account chooser to only those with the same domain.
    hd=flask.session.get("hd"))

Java

עוברים ל-method authorize() במחלקה AuthService.java. מוסיפים את הארגומנטים login_hint ו-hd כפרמטרים לשיטה, ומוסיפים את הארגומנטים login_hint ו-hd לכלי ליצירת כתובות URL להרשאות.

String authUrl = flow
    .newAuthorizationUrl()
    .setState(state)
    .set("login_hint", login_hint)
    .set("hd", hd)
    .setRedirectUri(REDIRECT_URI)
    .build();

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

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

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

הגדרת סכימת המשתמשים והגדרת מסד הנתונים

הגדרת סכימה של מסד נתונים ל-User.

Python

מגדירים את סכימת המשתמשים

השדה User כולל את המאפיינים הבאים:

  • id: מזהה Google של המשתמש. הערך הזה צריך להתאים לערכים שצוינו בפרמטר השאילתה login_hint.
  • display_name: השם הפרטי ושם המשפחה של המשתמש, למשל "אלי כהן".
  • email: כתובת האימייל של המשתמש.
  • portrait_url: כתובת ה-URL של תמונת הפרופיל של המשתמש.
  • refresh_token: אסימון הרענון שנרכש בעבר.

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

הגדרת מסד הנתונים

תחילה, יש לציין את מיקום הקובץ למסד הנתונים שלנו. מנווטים לקובץ תצורת השרת (config.py בדוגמה שסיפקנו) ומוסיפים את הקוד הבא.

import os

# Point to a database file in the project root.
DATABASE_FILE_NAME = os.path.join(
    os.path.abspath(os.path.dirname(__file__)), 'data.sqlite')

class Config(object):
    SQLALCHEMY_DATABASE_URI = f"sqlite:///{DATABASE_FILE_NAME}"
    SQLALCHEMY_TRACK_MODIFICATIONS = False

הפעולה הזו מפנה את Flask לקובץ data.sqlite שנמצא באותה ספרייה שבה נמצא הקובץ main.py.

בשלב הבא, מנווטים לספריית המודול ויוצרים קובץ models.py חדש. הערך הוא webapp/models.py אם אתם פועלים לפי הדוגמה שלנו. מוסיפים את השורה הבאה לקובץ החדש כדי להגדיר את הטבלה User, ומחליפים את שם המודול של webapp אם הוא שונה.

from webapp import db

# Database model to represent a user.
class User(db.Model):
    # The user's identifying information:
    id = db.Column(db.String(120), primary_key=True)
    display_name = db.Column(db.String(80))
    email = db.Column(db.String(120), unique=True)
    portrait_url = db.Column(db.Text())

    # The user's refresh token, which will be used to obtain an access token.
    # Note that refresh tokens will become invalid if:
    # - The refresh token has not been used for six months.
    # - The user revokes your app's access permissions.
    # - The user changes passwords.
    # - The user belongs to a Google Cloud organization
    #   that has session control policies in effect.
    refresh_token = db.Column(db.Text())

לסיום, בקובץ __init__.py של המודול, מוסיפים את הקוד הבא כדי לייבא את המודלים החדשים וליצור את מסד הנתונים.

from webapp import models
from os import path
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

# Initialize the database file if not created.
if not path.exists(config.DATABASE_FILE_NAME):
    db.create_all()

Java

מגדירים את סכימת המשתמשים

השדה User כולל את המאפיינים הבאים:

  • id: מזהה Google של המשתמש. הוא צריך להיות תואם לערך שצוין בפרמטר השאילתה login_hint.
  • email: כתובת האימייל של המשתמש.

יוצרים קובץ schema.sql בספרייה resources של המודול. מערכת Spring קוראת את הקובץ הזה ויוצרת סכימה למסד הנתונים בהתאם. מגדירים את הטבלה באמצעות שם הטבלה, users, ועמודות שייצגו את המאפיינים של User, id ו-email.

CREATE TABLE IF NOT EXISTS users (
    id VARCHAR(255) PRIMARY KEY, -- user's unique Google ID
    email VARCHAR(255), -- user's email address
);

צור מחלקת Java כדי להגדיר את המודל User למסד הנתונים. הערך של User.java בדוגמה שסופקה.

צריך להוסיף את ההערה @Entity כדי לציין שמדובר ב-POJO שאפשר לשמור במסד הנתונים. מוסיפים את ההערה @Table עם שם הטבלה המתאים שהגדרתם ב-schema.sql.

שימו לב שהדוגמה של הקוד כוללת בנאים ואופרטורים של שני המאפיינים. ה-constructor והגורמים המגדירים משמשים ב-AuthController.java כדי ליצור או לעדכן משתמש במסד הנתונים. אפשר גם לכלול getter ושיטת toString לפי הצורך, אבל בהדרכה המפורטת הזו המערכת לא משתמשת בשיטות האלה והן מוסרות מדוגמת הקוד שבדף הזה לשם קיצור.

/** An entity class that provides a model to store user information. */
@Entity
@Table(name = "users")
public class User {
    /** The user's unique Google ID. The @Id annotation specifies that this
     *   is the primary key. */
    @Id
    @Column
    private String id;

    /** The user's email address. */
    @Column
    private String email;

    /** Required User class no args constructor. */
    public User() {
    }

    /** The User class constructor that creates a User object with the
    *   specified parameters.
    *   @param id the user's unique Google ID
    *   @param email the user's email address
    */
    public User(String id, String email) {
        this.id = id;
        this.email = email;
    }

    public void setId(String id) { this.id = id; }

    public void setEmail(String email) { this.email = email; }
}

יצירת ממשק בשם UserRepository.java לטיפול בפעולות CRUD במסד הנתונים. הממשק הזה מרחיב את הממשק של CrudRepository.

/** Provides CRUD operations for the User class by extending the
 *   CrudRepository interface. */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}

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

/** Declare UserRepository to be used in the Controller class constructor. */
private final UserRepository userRepository;

/**
*   ...
*   @param userRepository the class that interacts with User objects stored in
*   persistent storage.
*/
public AuthController(AuthService authService, UserRepository userRepository) {
    this.authService = authService;
    this.userRepository = userRepository;
}

הגדרת מסד הנתונים

כדי לאחסן מידע שקשור למשתמש, צריך להשתמש במסד נתונים H2 שנתמך באופן מהותי ב-Spring Boot. מסד הנתונים הזה משמש גם בהדרכות מפורטות בנושא, כדי לאחסן מידע אחר שקשור ל-Classroom. כדי להגדיר את מסד הנתונים H2 צריך להוסיף את ההגדרות הבאות ל-application.properties.

# Enable configuration for persistent storage using an H2 database
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:file:./h2/userdb
spring.datasource.username=<USERNAME>
spring.datasource.password=<PASSWORD>
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false

ההגדרה של spring.datasource.url יוצרת ספרייה בשם h2 שמכילה את הקובץ userdb. מוסיפים את הנתיב למסד הנתונים H2 ל-.gitignore. צריך לעדכן את spring.datasource.username ואת spring.datasource.password לפני שמריצים את האפליקציה כדי להגדיר את מסד הנתונים עם שם משתמש וסיסמה לבחירתכם. כדי לעדכן את שם המשתמש והסיסמה במסד הנתונים אחרי הרצת האפליקציה, צריך למחוק את ספריית h2 שנוצרה, לעדכן את ההגדרות האישיות ולהפעיל מחדש את האפליקציה.

אם מגדירים את הערך update בהגדרה spring.jpa.hibernate.ddl-auto, הנתונים שמאוחסנים במסד הנתונים נשמרים אחרי שמפעילים מחדש את האפליקציה. כדי לנקות את מסד הנתונים בכל פעם שהאפליקציה מופעלת מחדש, צריך להגדיר את הערך create.

קובעים את ההגדרה spring.jpa.open-in-view לערך false. ההגדרה הזו מופעלת כברירת מחדל ועלולה לגרום לבעיות בביצועים שקשה לאבחן בסביבת הייצור.

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

במחלקה AuthService.java, הגדר נתיב לקובץ שבו מאוחסנת המחלקה של פרטי הכניסה. בדוגמה הזו הקובץ נוצר בספרייה /credentialStore. מוסיפים את הנתיב למאגר פרטי הכניסה ב-.gitignore. הספרייה הזו נוצרת כשהמשתמש מתחיל את תהליך ההרשאה.

private static final File dataDirectory = new File("credentialStore");

בשלב הבא, יוצרים שיטה בקובץ AuthService.java שיוצרת ומחזירה אובייקט FileDataStoreFactory. זהו מאגר הנתונים שבו מאוחסנים פרטי הכניסה.

/** Creates and returns FileDataStoreFactory object to store credentials.
 *   @return FileDataStoreFactory dataStore used to save and obtain users ids
 *   mapped to Credentials.
 *   @throws IOException if creating the dataStore is unsuccessful.
 */
public FileDataStoreFactory getCredentialDataStore() throws IOException {
    FileDataStoreFactory dataStore = new FileDataStoreFactory(dataDirectory);
    return dataStore;
}

צריך לעדכן את השיטה getFlow() ב-AuthService.java כך שתכלול את setDataStoreFactory בשיטה GoogleAuthorizationCodeFlow Builder() ולקריאה ל-getCredentialDataStore() כדי להגדיר את מאגר הנתונים.

GoogleAuthorizationCodeFlow authorizationCodeFlow =
    new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY,
        getClientSecrets(),
        getScopes())
    .setAccessType("offline")
    .setDataStoreFactory(getCredentialDataStore())
    .build();

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

אפשר לקבל את מזהה המשתמש מהאובייקט TokenResponse באמצעות id_token, אבל צריך לאמת אותו קודם. אחרת, אפליקציות לקוח יכולות להתחזות למשתמשים על ידי שליחת מזהי משתמשים שעברו שינוי לשרת. מומלץ להשתמש בספריות הלקוח של Google API כדי לאמת את id_token. מידע נוסף זמין ב[דף של Google Identity בנושא אימות האסימון של Google ID].

// Obtaining the id_token will help determine which user signed in to the application.
String idTokenString = tokenResponse.get("id_token").toString();

// Validate the id_token using the GoogleIdTokenVerifier object.
GoogleIdTokenVerifier googleIdTokenVerifier = new GoogleIdTokenVerifier.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY)
    .setAudience(Collections.singletonList(
        googleClientSecrets.getWeb().getClientId()))
    .build();

GoogleIdToken idToken = googleIdTokenVerifier.verify(idTokenString);

if (idToken == null) {
    throw new Exception("Invalid ID token.");
}

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

// Obtain the user id from the id_token.
Payload payload = idToken.getPayload();
String userId = payload.getSubject();

צריך לעדכן את השיחה ל-flow.createAndStoreCredential כך שתכלול את userId.

// Save the user id and credentials to the configured FileDataStoreFactory.
Credential credential = flow.createAndStoreCredential(tokenResponse, userId);

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

/** Find credentials in the datastore based on a specific user id.
*   @param userId key to find in the file datastore.
*   @return Credential object to be returned if a matching key is found in the datastore. Null if
*   the key doesn't exist.
*   @throws Exception if building flow object or checking for userId key is unsuccessful. */
public Credential loadFromCredentialDataStore(String userId) throws Exception {
    try {
        GoogleAuthorizationCodeFlow flow = getFlow();
        Credential credential = flow.loadCredential(userId);
        return credential;
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
}

אחזור פרטי הכניסה

הגדרה של שיטה לאחזור Users. קיבלתם id בפרמטר השאילתה login_hint, ואפשר להשתמש בו כדי לאחזר רשומה ספציפית של משתמש.

Python

def get_credentials_from_storage(id):
    """
    Retrieves credentials from the storage and returns them as a dictionary.
    """
    return User.query.get(id)

Java

במחלקה AuthController.java, מגדירים שיטה לאחזור משתמש ממסד הנתונים על סמך מזהה המשתמש שלו.

/** Retrieves stored credentials based on the user id.
*   @param id the id of the current user
*   @return User the database entry corresponding to the current user or null
*   if the user doesn't exist in the database.
*/
public User getUser(String id) {
    if (id != null) {
        Optional<User> user = userRepository.findById(id);
        if (user.isPresent()) {
            return user.get();
        }
    }
    return null;
}

פרטי הכניסה לאחסון

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

Python

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

def save_user_credentials(credentials=None, user_info=None):
    """
    Updates or adds a User to the database. A new user is added only if both
    credentials and user_info are provided.

    Args:
        credentials: An optional Credentials object.
        user_info: An optional dict containing user info returned by the
            OAuth 2.0 API.
    """

    existing_user = get_credentials_from_storage(
        flask.session.get("login_hint"))

    if existing_user:
        if user_info:
            existing_user.id = user_info.get("id")
            existing_user.display_name = user_info.get("name")
            existing_user.email = user_info.get("email")
            existing_user.portrait_url = user_info.get("picture")

        if credentials and credentials.refresh_token is not None:
            existing_user.refresh_token = credentials.refresh_token

    elif credentials and user_info:
        new_user = User(id=user_info.get("id"),
                        display_name=user_info.get("name"),
                        email=user_info.get("email"),
                        portrait_url=user_info.get("picture"),
                        refresh_token=credentials.refresh_token)

        db.session.add(new_user)

    db.session.commit()

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

יש להתקשר אל save_user_credentials בסוף המסלול callback. שומרים את האובייקט user_info ולא רק לחלץ את שם המשתמש.

# The flow is complete! We'll use the credentials to fetch the user's info.
user_info_service = googleapiclient.discovery.build(
    serviceName="oauth2", version="v2", credentials=credentials)

user_info = user_info_service.userinfo().get().execute()

flask.session["username"] = user_info.get("name")

save_user_credentials(credentials, user_info)

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

# Save credentials in case access token was refreshed.
flask.session["credentials"] = credentials_to_dict(credentials)
save_user_credentials(credentials)

Java

קודם כול צריך להגדיר שיטה שמאחסנת או מעדכנת אובייקט User במסד הנתונים H2.

/** Adds or updates a user in the database.
*   @param credential the credentials object to save or update in the database.
*   @param userinfo the userinfo object to save or update in the database.
*   @param session the current session.
*/
public void saveUser(Credential credential, Userinfo userinfo, HttpSession session) {
    User storedUser = null;
    if (session != null && session.getAttribute("login_hint") != null) {
        storedUser = getUser(session.getAttribute("login_hint").toString());
    }

    if (storedUser != null) {
        if (userinfo != null) {
            storedUser.setId(userinfo.getId());
            storedUser.setEmail(userinfo.getEmail());
        }
        userRepository.save(storedUser);
    } else if (credential != null && userinfo != null) {
        User newUser = new User(
            userinfo.getId(),
            userinfo.getEmail(),
        );
        userRepository.save(newUser);
    }
}

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

התקשרות אל saveUser בסוף המסלול /callback. יש לשמור את האובייקט user_info ולא רק לחלץ את כתובת האימייל של המשתמש.

/** This is the end of the auth flow. We should save user info to the database. */
Userinfo userinfo = authService.getUserInfo(credentials);
saveUser(credentials, userinfo, session);

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

/** Save credentials in case access token was refreshed. */
saveUser(credentials, null, session);

פרטי כניסה שפג תוקפם

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

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

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

ניתוב אוטומטי של המשתמש

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

Python

מוודאים שקובץ מסד הנתונים נוצר כשהאפליקציה מופעלת. מזינים את הקוד הבא לאתחול המודול (למשל webapp/__init__.py בדוגמה שסופקה), או ל-method הראשי שמפעיל את השרת.

# Initialize the database file if not created.
if not os.path.exists(DATABASE_FILE_NAME):
    db.create_all()

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

stored_credentials = get_credentials_from_storage(login_hint)

# If we have stored credentials, store them in the session.
if stored_credentials:
    # Load the client secrets file contents.
    client_secrets_dict = json.load(
        open(CLIENT_SECRETS_FILE)).get("web")

    # Update the credentials in the session.
    if not flask.session.get("credentials"):
        flask.session["credentials"] = {}

    flask.session["credentials"] = {
        "token": stored_credentials.access_token,
        "refresh_token": stored_credentials.refresh_token,
        "token_uri": client_secrets_dict["token_uri"],
        "client_id": client_secrets_dict["client_id"],
        "client_secret": client_secrets_dict["client_secret"],
        "scopes": SCOPES
    }

    # Set the username in the session.
    flask.session["username"] = stored_credentials.display_name

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

if "credentials" not in flask.session or \
    flask.session["credentials"]["refresh_token"] is None:
    return flask.render_template("authorization.html")

return flask.render_template(
    "addon-discovery.html",
    message="You've reached the addon discovery page.")

Java

עוברים למסלול הנחיתה של התוסף (/addon-discovery בדוגמה שסופקה). כפי שהסברנו למעלה, כאן הטיפול בפרמטרים של השאילתה login_hint ו-hd.

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

/** Check if the credentials exist in the session. The session could have
 *   been cleared when the user clicked the Sign-Out button, and the expected
 *   behavior after sign-out would be to display the sign-in page when the
 *   iframe is opened again. */
if (session.getAttribute("credentials") == null) {
    return startAuthFlow(model);
}

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

/** At this point, we know that credentials exist in the session, but we
 *   should update the session credentials with the credentials in persistent
 *   storage in case they were refreshed. If the credentials in persistent
 *   storage are null, we should navigate the user to the authorization flow
 *   to obtain persisted credentials. */

User storedUser = getUser(login_hint);

if (storedUser != null) {
    Credential credential = authService.loadFromCredentialDataStore(login_hint);
    if (credential != null) {
        session.setAttribute("credentials", credential);
    } else {
        return startAuthFlow(model);
    }
}

לבסוף, מנתבים את המשתמשים לדף הנחיתה של התוסף.

/** Finally, if there are credentials in the session and in persistent
 *   storage, direct the user to the addon-discovery page. */
return "addon-discovery";

בדיקת התוסף

נכנסים ל-Google Classroom בתור אחד מהמשתמשים בבחינה של המורים. מנווטים לכרטיסייה עבודות ויוצרים מטלה חדשה. לוחצים על הלחצן תוספים מתחת לאזור הטקסט ובוחרים את התוסף. ה-iframe נפתח והתוסף יטען את ה-URI להגדרת קבצים מצורפים שציינת בדף הגדרת אפליקציה של GWM SDK.

כל הכבוד! אתם מוכנים להמשיך לשלב הבא: יצירת קבצים מצורפים וזיהוי התפקיד של המשתמש.