הכנסת המשתמש

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

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

במהלך ההדרכה המפורטת הזו, תצטרכו:

  • מגדירים את אפליקציית האינטרנט כדי לשמור על נתוני הסשנים ב-iframe.
  • הטמעת תהליך כניסה מסוג שרת-אל-שרת של Google OAuth 2.0.
  • לשלוח קריאה ל-OAuth 2.0 API.
  • יצירת מסלולים נוספים לתמיכה באישור, ביציאה ובבדיקה של קריאות ל-API.

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

הסבר על תהליך ההרשאה

ממשקי Google API משתמשים בפרוטוקול OAuth 2.0 לצורך אימות והרשאה. התיאור המלא של הטמעת OAuth של Google זמין במדריך בנושא OAuth ב-Google Identity.

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

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

קבלת פרטי כניסה בפרוטוקול OAuth 2.0

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

הטמעת תהליך ההרשאה

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

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

הפעלת ההרשאה

משנים את דף הנחיתה כדי להתחיל את תהליך ההרשאה במקרה הצורך. התוסף יכול להיות בשני מצבים אפשריים: יש אסימונים שמורים בסשן הנוכחי או שצריך לקבל אסימונים משרת OAuth 2.0. מבצעים קריאה ל-API לבדיקה אם יש אסימונים בסשן, או מבקשים מהמשתמש להיכנס לחשבון באופן אחר.

Python

פותחים את הקובץ routes.py. קודם כול, מגדירים מספר קבועים ואת ההגדרות של קובצי ה-Cookie בהתאם להמלצות האבטחה ב-iframe.

# The file that contains the OAuth 2.0 client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"

# The OAuth 2.0 access scopes to request.
# These scopes must match the scopes in your Google Cloud project's OAuth Consent
# Screen: https://console.cloud.google.com/apis/credentials/consent
SCOPES = [
    "openid",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/classroom.addons.teacher",
    "https://www.googleapis.com/auth/classroom.addons.student"
]

# Flask cookie configurations.
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="None",
)

עוברים למסלול הנחיתה של התוסף (בקובץ לדוגמה הערך /classroom-addon). צריך להוסיף לוגיקה לעיבוד דף כניסה אם הסשן לא מכיל את המפתח 'פרטי כניסה'.

@app.route("/classroom-addon")
def classroom_addon():
    if "credentials" not in flask.session:
        return flask.render_template("authorization.html")

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

Java

הקוד להדרכה המפורטת הזו מופיע במודול step_02_sign_in.

פותחים את הקובץ application.properties ומוסיפים הגדרות אישיות של סשן בהתאם להמלצות האבטחה של iframe.

# iFrame security recommendations call for cookies to have the HttpOnly and
# secure attribute set
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true

# Ensures that the session is maintained across the iframe and sign-in pop-up.
server.servlet.session.cookie.same-site=none

יוצרים סוג שירות (AuthService.java במודול step_02_sign_in) כדי לטפל בלוגיקה שמאחורי נקודות הקצה בקובץ הבקרה, ולהגדיר את ה-URI להפניה אוטומטית, את מיקום קובץ הסודות של הלקוח ואת ההיקפים שנדרשים לתוסף. ה-URI להפניה אוטומטית משמש לניתוב מחדש של המשתמשים ל-URI ספציפי אחרי שהם אישרו את האפליקציה. עיינו בקטע הגדרת פרויקט ב-README.md בקוד המקור למידע על המיקום של קובץ client_secret.json.

@Service
public class AuthService {
    private static final String REDIRECT_URI = "https://localhost:5000/callback";
    private static final String CLIENT_SECRET_FILE = "client_secret.json";
    private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
    private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();

    private static final String[] REQUIRED_SCOPES = {
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/classroom.addons.teacher",
        "https://www.googleapis.com/auth/classroom.addons.student"
    };

    /** Creates and returns a Collection object with all requested scopes.
    *   @return Collection of scopes requested by the application.
    */
    public static Collection<String> getScopes() {
        return new ArrayList<>(Arrays.asList(REQUIRED_SCOPES));
    }
}

פותחים את קובץ השלט רחוק (AuthController.java במודול step_02_sign_in) ומוסיפים לוגיקה למסלול הנחיתה כדי לעבד את דף הכניסה אם הסשן לא מכיל את המפתח credentials.

@GetMapping(value = {"/start-auth-flow"})
public String startAuthFlow(Model model) {
    try {
        return "authorization";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(HttpSession session, Model model) {
    try {
        if (session == null || session.getAttribute("credentials") == null) {
            return startAuthFlow(model);
        }
        return "addon-discovery";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

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

בקשת הרשאה

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

Python

צריך להוסיף את הייבוא הבא לקובץ routes.py.

import google_auth_oauthlib.flow

יצירת מסלול חדש /authorize. יוצרים מכונה של google_auth_oauthlib.flow.Flow. מומלץ מאוד להשתמש בשיטה from_client_secrets_file הכלולה.

@app.route("/authorize")
def authorize():
    # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow
    # steps.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES)

מגדירים את redirect_uri של flow. זה המסלול שאליו רוצים שהמשתמשים יחזרו אחרי שיאשרו את האפליקציה. בדוגמה הבאה הערך הוא /callback.

# The URI created here must exactly match one of the authorized redirect
# URIs for the OAuth 2.0 client, which you configured in the API Console. If
# this value doesn't match an authorized URI, you will get a
# "redirect_uri_mismatch" error.
flow.redirect_uri = flask.url_for("callback", _external=True)

משתמשים באובייקט הזרימה כדי ליצור את authorization_url ואת state. מאחסנים את ה-state בסשן. הוא משמש לאימות האותנטיות של תגובת השרת מאוחר יותר. לסיום, מפנים את המשתמש אל 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")

# Store the state so the callback can verify the auth server response.
flask.session["state"] = state

# Redirect the user to the OAuth authorization URL.
return flask.redirect(authorization_url)

Java

מוסיפים את השיטות הבאות לקובץ AuthService.java כדי ליצור את אובייקט הזרימה, ואז משתמשים בו כדי לאחזר את כתובת ה-URL של ההרשאה:

  • ה-method getClientSecrets() קוראת את הקובץ של סוד הלקוח ויוצרת אובייקט GoogleClientSecrets.
  • השיטה getFlow() יוצרת מופע של GoogleAuthorizationCodeFlow.
  • ה-method authorize() משתמשת באובייקט GoogleAuthorizationCodeFlow, בפרמטר state וב-URI של ההפניה האוטומטית כדי לאחזר את כתובת ה-URL של ההרשאה. הפרמטר state משמש לאימות האותנטיות של התשובה משרת ההרשאות. לאחר מכן, ה-method מחזירה מפה עם כתובת ה-URL של ההרשאה והפרמטר state.
/** Reads the client secret file downloaded from Google Cloud.
 *   @return GoogleClientSecrets read in from client secret file. */
public GoogleClientSecrets getClientSecrets() throws Exception {
    try {
        InputStream in = SignInApplication.class.getClassLoader()
            .getResourceAsStream(CLIENT_SECRET_FILE);
        if (in == null) {
            throw new FileNotFoundException("Client secret file not found: "
                +   CLIENT_SECRET_FILE);
        }
        GoogleClientSecrets clientSecrets = GoogleClientSecrets
            .load(JSON_FACTORY, new InputStreamReader(in));
        return clientSecrets;
    } catch (Exception e) {
        throw e;
    }
}

/** Builds and returns authorization code flow.
*   @return GoogleAuthorizationCodeFlow object used to retrieve an access
*   token and refresh token for the application.
*   @throws Exception if reading client secrets or building code flow object
*   is unsuccessful.
*/
public GoogleAuthorizationCodeFlow getFlow() throws Exception {
    try {
        GoogleAuthorizationCodeFlow authorizationCodeFlow =
            new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT,
                JSON_FACTORY,
                getClientSecrets(),
                getScopes())
                .setAccessType("offline")
                .build();
        return authorizationCodeFlow;
    } catch (Exception e) {
        throw e;
    }
}

/** Builds and returns a map with the authorization URL, which allows the
*   user to give the app permission to their account, and the state parameter,
*   which is used to prevent cross site request forgery.
*   @return map with authorization URL and state parameter.
*   @throws Exception if building the authorization URL is unsuccessful.
*/
public HashMap authorize() throws Exception {
    HashMap<String, String> authDataMap = new HashMap<>();
    try {
        String state = new BigInteger(130, new SecureRandom()).toString(32);
        authDataMap.put("state", state);

        GoogleAuthorizationCodeFlow flow = getFlow();
        String authUrl = flow
            .newAuthorizationUrl()
            .setState(state)
            .setRedirectUri(REDIRECT_URI)
            .build();
        String url = authUrl;
        authDataMap.put("url", url);

        return authDataMap;
    } catch (Exception e) {
        throw e;
    }
}

משתמשים בהחדרת constructor כדי ליצור מכונה של סוג השירות במחלקה של הבקרה.

/** Declare AuthService to be used in the Controller class constructor. */
private final AuthService authService;

/** AuthController constructor. Uses constructor injection to instantiate
*   the AuthService and UserRepository classes.
*   @param authService the service class that handles the implementation logic
*   of requests.
*/
public AuthController(AuthService authService) {
    this.authService = authService;
}

מוסיפים את נקודת הקצה (endpoint) /authorize למחלקה של הבקרה. נקודת הקצה (endpoint) הזו מפעילה את method authorize() של AuthService כדי לאחזר את הפרמטר state ואת כתובת ה-URL של ההרשאה. לאחר מכן, נקודת הקצה שומרת את הפרמטר state בסשן ומפנה את המשתמשים לכתובת ה-URL של ההרשאה.

/** Redirects the sign-in pop-up to the authorization URL.
*   @param response the current response to pass information to.
*   @param session the current session.
*   @throws Exception if redirection to the authorization URL is unsuccessful.
*/
@GetMapping(value = {"/authorize"})
public void authorize(HttpServletResponse response, HttpSession session)
    throws Exception {
    try {
        HashMap authDataMap = authService.authorize();
        String authUrl = authDataMap.get("url").toString();
        String state = authDataMap.get("state").toString();
        session.setAttribute("state", state);
        response.sendRedirect(authUrl);
    } catch (Exception e) {
        throw e;
    }
}

טיפול בתגובת השרת

אחרי מתן ההרשאה, המשתמש חוזר למסלול redirect_uri מהשלב הקודם. בדוגמה הקודמת, המסלול הזה הוא /callback.

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

Python

מוסיפים את פעולות הייבוא הבאות לקובץ שרת Flask.

import google.oauth2.credentials
import googleapiclient.discovery

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

@app.route("/callback")
def callback():
    state = flask.session["state"]

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
    flow.redirect_uri = flask.url_for("callback", _external=True)

בשלב הבא צריך לבקש אסימוני גישה ורענון. למרבה המזל, האובייקט flow מכיל גם את ה-method fetch_token. ה-method מצפה לארגומנטים code או authorization_response. צריך להשתמש ב-authorization_response, כי זו כתובת ה-URL המלאה מהבקשה.

authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

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

credentials = flow.credentials
flask.session["credentials"] = {
    "token": credentials.token,
    "refresh_token": credentials.refresh_token,
    "token_uri": credentials.token_uri,
    "client_id": credentials.client_id,
    "client_secret": credentials.client_secret,
    "scopes": credentials.scopes
}

# Close the pop-up by rendering an HTML page with a script that redirects
# the owner and closes itself. This can be done with a bit of JavaScript:
# <script>
#     window.opener.location.href = "{{ url_for('classroom_addon') }}";
#     window.close();
# </script>
return flask.render_template("close-me.html")

Java

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

/** Returns the required credentials to access Google APIs.
*   @param authorizationCode the authorization code provided by the
*   authorization URL that's used to obtain credentials.
*   @return the credentials that were retrieved from the authorization flow.
*   @throws Exception if retrieving credentials is unsuccessful.
*/
public Credential getAndSaveCredentials(String authorizationCode) throws Exception {
    try {
        GoogleAuthorizationCodeFlow flow = getFlow();
        GoogleClientSecrets googleClientSecrets = getClientSecrets();
        TokenResponse tokenResponse = flow.newTokenRequest(authorizationCode)
            .setClientAuthentication(new ClientParametersAuthentication(
                googleClientSecrets.getWeb().getClientId(),
                googleClientSecrets.getWeb().getClientSecret()))
            .setRedirectUri(REDIRECT_URI)
            .execute();
        Credential credential = flow.createAndStoreCredential(tokenResponse, null);
        return credential;
    } catch (Exception e) {
        throw e;
    }
}

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

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

/** Handles the redirect URL to grant the application access to the user's
*   account.
*   @param request the current request used to obtain the authorization code
*   and state parameter from.
*   @param session the current session.
*   @param response the current response to pass information to.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the close-pop-up template if authorization is successful, or the
*   onError method to handle and display the error message.
*/
@GetMapping(value = {"/callback"})
public String callback(HttpServletRequest request, HttpSession session,
    HttpServletResponse response, Model model) {
    try {
        String authCode = request.getParameter("code");
        String requestState = request.getParameter("state");
        String sessionState = session.getAttribute("state").toString();
        if (!requestState.equals(sessionState)) {
            response.setStatus(401);
            return onError("Invalid state parameter.", model);
        }
        Credential credentials = authService.getAndSaveCredentials(authCode);
        session.setAttribute("credentials", credentials);
        return "close-pop-up";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

בדיקת קריאה ל-API

כשהתהליך הושלם, אפשר עכשיו לשלוח קריאות ל-Google APIs.

לדוגמה, אפשר לבקש את פרטי הפרופיל של המשתמש. תוכלו לבקש את פרטי המשתמש מ-OAuth 2.0 API.

Python

קראו את מסמכי התיעוד של OAuth 2.0 Discovery API. תוכלו להשתמש בו כדי לקבל אובייקט UserInfo מאוכלס.

# Retrieve the credentials from the session data and construct a
# Credentials instance.
credentials = google.oauth2.credentials.Credentials(
    **flask.session["credentials"])

# Construct the OAuth 2.0 v2 discovery API library.
user_info_service = googleapiclient.discovery.build(
    serviceName="oauth2", version="v2", credentials=credentials)

# Request and store the username in the session.
# This allows it to be used in other methods or in an HTML template.
flask.session["username"] = (
    user_info_service.userinfo().get().execute().get("name"))

Java

יוצרים שיטה במחלקת השירות שיוצרת אובייקט UserInfo תוך שימוש ב-Credentials כפרמטר.

/** Obtains the Userinfo object by passing in the required credentials.
*   @param credentials retrieved from the authorization flow.
*   @return the Userinfo object for the currently signed-in user.
*   @throws IOException if creating UserInfo service or obtaining the
*   Userinfo object is unsuccessful.
*/
public Userinfo getUserInfo(Credential credentials) throws IOException {
    try {
        Oauth2 userInfoService = new Oauth2.Builder(
            new NetHttpTransport(),
            new GsonFactory(),
            credentials).build();
        Userinfo userinfo = userInfoService.userinfo().get().execute();
        return userinfo;
    } catch (Exception e) {
        throw e;
    }
}

מוסיפים את נקודת הקצה (endpoint) /test לבקר שמציג את כתובת האימייל של המשתמש.

/** Returns the test request page with the user's email.
*   @param session the current session.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the test page that displays the current user's email or the
*   onError method to handle and display the error message.
*/
@GetMapping(value = {"/test"})
public String test(HttpSession session, Model model) {
    try {
        Credential credentials = (Credential) session.getAttribute("credentials");
        Userinfo userInfo = authService.getUserInfo(credentials);
        String userInfoEmail = userInfo.getEmail();
        if (userInfoEmail != null) {
            model.addAttribute("userEmail", userInfoEmail);
        } else {
            return onError("Could not get user email.", model);
        }
        return "test";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

ניקוי פרטי כניסה

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

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

Python

@app.route("/clear")
def clear_credentials():
    if "credentials" in flask.session:
        del flask.session["credentials"]
        del flask.session["username"]

    return flask.render_template("signed-out.html")

לחלופין, אפשר להשתמש ב-flask.session.clear(), אבל עלולות להיות לכך השפעות לא רצויות אם שמורים ערכים אחרים בסשן.

Java

בבקר, מוסיפים נקודת קצה (endpoint) /clear.

/** Clears the credentials in the session and returns the sign-out
*   confirmation page.
*   @param session the current session.
*   @return the sign-out confirmation page.
*/
@GetMapping(value = {"/clear"})
public String clear(HttpSession session) {
    try {
        if (session != null && session.getAttribute("credentials") != null) {
            session.removeAttribute("credentials");
        }
        return "sign-out";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

ביטול ההרשאה של האפליקציה

משתמש יכול לבטל את ההרשאה של האפליקציה על ידי שליחת בקשת POST אל https://oauth2.googleapis.com/revoke. הבקשה צריכה להכיל את אסימון הגישה של המשתמש.

Python

import requests

@app.route("/revoke")
def revoke():
    if "credentials" not in flask.session:
        return flask.render_template("addon-discovery.html",
                            message="You need to authorize before " +
                            "attempting to revoke credentials.")

    credentials = google.oauth2.credentials.Credentials(
        **flask.session["credentials"])

    revoke = requests.post(
        "https://oauth2.googleapis.com/revoke",
        params={"token": credentials.token},
        headers={"content-type": "application/x-www-form-urlencoded"})

    if "credentials" in flask.session:
        del flask.session["credentials"]
        del flask.session["username"]

    status_code = getattr(revoke, "status_code")
    if status_code == 200:
        return flask.render_template("authorization.html")
    else:
        return flask.render_template(
            "index.html", message="An error occurred during revocation!")

Java

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

/** Revokes the app's permissions to the user's account.
*   @param credentials retrieved from the authorization flow.
*   @return response entity returned from the HTTP call to obtain response
*   information.
*   @throws RestClientException if the POST request to the revoke endpoint is
*   unsuccessful.
*/
public ResponseEntity<String> revokeCredentials(Credential credentials) throws RestClientException {
    try {
        String accessToken = credentials.getAccessToken();
        String url = "https://oauth2.googleapis.com/revoke?token=" + accessToken;

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
        HttpEntity<Object> httpEntity = new HttpEntity<Object>(httpHeaders);
        ResponseEntity<String> responseEntity = new RestTemplate().exchange(
            url,
            HttpMethod.POST,
            httpEntity,
            String.class);
        return responseEntity;
    } catch (RestClientException e) {
        throw e;
    }
}

מוסיפים לבקר נקודת קצה (endpoint) /revoke, שמבטלת את הסשן ומפנה מחדש את המשתמשים לדף ההרשאה אם הביטול בוצע.

/** Revokes the app's permissions and returns the authorization page.
*   @param session the current session.
*   @return the authorization page.
*   @throws Exception if revoking access is unsuccessful.
*/
@GetMapping(value = {"/revoke"})
public String revoke(HttpSession session) throws Exception {
    try {
        if (session != null && session.getAttribute("credentials") != null) {
            Credential credentials = (Credential) session.getAttribute("credentials");
            ResponseEntity responseEntity = authService.revokeCredentials(credentials);
            Integer httpStatusCode = responseEntity.getStatusCodeValue();

            if (httpStatusCode != 200) {
                return onError("There was an issue revoking access: " +
                    responseEntity.getStatusCode(), model);
            }
            session.removeAttribute("credentials");
        }
        return startAuthFlow(model);
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

בדיקת התוסף

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

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