Đăng nhập người dùng

Đây là hướng dẫn thứ hai trong chuỗi hướng dẫn từng bước về tiện ích bổ sung trong Lớp học.

Trong hướng dẫn này, bạn sẽ thêm tính năng Đăng nhập bằng Google vào ứng dụng web. Đây là hành vi bắt buộc đối với các tiện ích bổ sung dành cho Lớp học. Sử dụng thông tin xác thực từ quy trình uỷ quyền này cho tất cả các lệnh gọi đến API trong tương lai.

Trong quá trình hướng dẫn này, bạn sẽ hoàn thành các bước sau:

  • Định cấu hình ứng dụng web để duy trì dữ liệu phiên trong iframe.
  • Triển khai quy trình đăng nhập từ máy chủ đến máy chủ của Google OAuth 2.0.
  • Gọi lệnh đến API OAuth 2.0.
  • Tạo các tuyến bổ sung để hỗ trợ việc uỷ quyền, đăng xuất và kiểm thử lệnh gọi API.

Sau khi hoàn tất, bạn có toàn quyền uỷ quyền cho người dùng trong ứng dụng web của mình và thực hiện lệnh gọi đến các API của Google.

Tìm hiểu quy trình uỷ quyền

API của Google sử dụng giao thức OAuth 2.0 để xác thực và uỷ quyền. Bạn có thể xem nội dung mô tả đầy đủ về quy trình triển khai OAuth của Google trong hướng dẫn về OAuth của Google Identity.

Thông tin đăng nhập của ứng dụng được quản lý trong Google Cloud. Sau khi các đối tượng này được tạo, hãy triển khai quy trình 4 bước để uỷ quyền và xác thực người dùng:

  1. Yêu cầu uỷ quyền. Cung cấp URL gọi lại trong yêu cầu này. Sau khi hoàn tất, bạn sẽ nhận được một URL uỷ quyền.
  2. Chuyển hướng người dùng đến URL uỷ quyền. Trang kết quả sẽ thông báo cho người dùng về các quyền mà ứng dụng của bạn yêu cầu và nhắc họ cấp quyền truy cập. Khi hoàn tất, người dùng được chuyển đến URL gọi lại.
  3. Nhận mã uỷ quyền tại tuyến gọi lại của bạn. Đổi mã uỷ quyền lấy mã truy cậpmã làm mới.
  4. Thực hiện lệnh gọi đến một API Google bằng mã thông báo.

Lấy thông tin đăng nhập OAuth 2.0

Đảm bảo rằng bạn đã tạo và tải thông tin đăng nhập OAuth xuống như mô tả trong Trang tổng quan. Dự án của bạn phải sử dụng những thông tin đăng nhập này để đăng nhập cho người dùng.

Triển khai quy trình uỷ quyền

Thêm logic và các tuyến vào ứng dụng web của chúng ta để hiện thực hoá quy trình được mô tả ở trên, bao gồm các tính năng sau:

  • Bắt đầu quy trình uỷ quyền khi truy cập vào trang đích.
  • Yêu cầu uỷ quyền và xử lý phản hồi của máy chủ uỷ quyền.
  • Xoá thông tin đăng nhập đã lưu trữ.
  • Thu hồi quyền của ứng dụng.
  • Kiểm thử lệnh gọi API.

Bắt đầu uỷ quyền

Sửa đổi trang đích của bạn để bắt đầu quy trình uỷ quyền nếu cần. Tiện ích bổ sung có thể ở hai trạng thái; có thể là có mã thông báo đã lưu trong phiên hiện tại hoặc bạn cần lấy mã thông báo từ máy chủ OAuth 2.0. Thực hiện lệnh gọi API kiểm thử nếu có mã thông báo trong phiên hoặc nhắc người dùng đăng nhập.

Python

Mở tệp routes.py. Trước tiên, hãy đặt một vài hằng số và cấu hình cookie của chúng tôi theo đề xuất về bảo mật 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",
)

Di chuyển đến tuyến đích của tiện ích bổ sung (đây là /classroom-addon trong tệp ví dụ). Thêm logic để hiển thị trang đăng nhập nếu phiên hoạt động không chứa khoá "thông tin xác thực".

@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

Bạn có thể tìm thấy mã cho hướng dẫn từng bước này trong mô-đun step_02_sign_in.

Mở tệp application.properties và thêm cấu hình phiên tuân theo các đề xuất về bảo mật 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

Tạo một lớp dịch vụ (AuthService.java trong mô-đun step_02_sign_in) để xử lý logic phía sau các điểm cuối trong tệp bộ điều khiển và thiết lập URI chuyển hướng, vị trí tệp bí mật ứng dụng khách cũng như phạm vi mà tiện ích bổ sung của bạn yêu cầu. URI chuyển hướng dùng để chuyển hướng người dùng đến một URI cụ thể sau khi họ cho phép ứng dụng của bạn. Hãy xem phần Thiết lập dự án của README.md trong mã nguồn để biết thông tin về vị trí đặt tệp 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));
    }
}

Mở tệp tay điều khiển (AuthController.java trong mô-đun step_02_sign_in) và thêm logic vào tuyến đích để kết xuất trang đăng nhập nếu phiên không chứa khoá 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);
    }
}

Trang uỷ quyền của bạn phải chứa một đường liên kết hoặc nút để người dùng "đăng nhập". Thao tác nhấp vào đây sẽ chuyển hướng người dùng đến tuyến authorize.

Yêu cầu uỷ quyền

Để yêu cầu cấp quyền, hãy tạo và chuyển hướng người dùng đến một URL xác thực. URL này bao gồm một số thông tin, chẳng hạn như phạm vi được yêu cầu, tuyến đích cho hoạt động uỷ quyền sau và mã ứng dụng khách của ứng dụng web. Bạn có thể thấy các thông tin này trong URL uỷ quyền mẫu này.

Python

Thêm nội dung nhập sau vào tệp routes.py.

import google_auth_oauthlib.flow

Tạo một tuyến đường mới /authorize. Tạo một thực thể của google_auth_oauthlib.flow.Flow; bạn nên sử dụng phương thức from_client_secrets_file đi kèm để thực hiện việc này.

@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)

Đặt redirect_uri của flow; đây là tuyến mà bạn muốn người dùng trả về sau khi uỷ quyền ứng dụng của bạn. Trong ví dụ dưới đây, đây là /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)

Sử dụng đối tượng luồng để tạo authorization_urlstate. Lưu trữ state trong phiên; dùng để xác minh tính xác thực của phản hồi của máy chủ sau này. Cuối cùng, hãy chuyển hướng người dùng đến 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

Thêm các phương thức sau vào tệp AuthService.java để tạo thực thể cho đối tượng luồng, sau đó sử dụng đối tượng này để truy xuất URL uỷ quyền:

  • Phương thức getClientSecrets() đọc tệp mật khẩu ứng dụng khách và tạo đối tượng GoogleClientSecrets.
  • Phương thức getFlow() tạo một thực thể của GoogleAuthorizationCodeFlow.
  • Phương thức authorize() sử dụng đối tượng GoogleAuthorizationCodeFlow, tham số state và URI chuyển hướng để truy xuất URL uỷ quyền. Tham số state dùng để xác minh tính xác thực của phản hồi từ máy chủ uỷ quyền. Sau đó, phương thức này sẽ trả về một bản đồ có URL uỷ quyền và tham số 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;
    }
}

Sử dụng tính năng chèn hàm khởi tạo để tạo một thực thể của lớp dịch vụ trong lớp trình điều khiển.

/** 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;
}

Thêm điểm cuối /authorize vào lớp tay điều khiển. Điểm cuối này gọi phương thức AuthService authorize() để truy xuất tham số state và URL uỷ quyền. Sau đó, điểm cuối lưu trữ tham số state trong phiên và chuyển hướng người dùng đến URL uỷ quyền.

/** 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;
    }
}

Xử lý phản hồi của máy chủ

Sau khi cho phép, người dùng quay lại tuyến redirect_uri từ bước trước. Trong ví dụ trên, tuyến này là /callback.

Bạn sẽ nhận được một code trong phản hồi khi người dùng quay lại từ trang uỷ quyền. Sau đó đổi mã để lấy quyền truy cập và làm mới mã:

Python

Thêm các lệnh nhập sau vào tệp máy chủ Flask.

import google.oauth2.credentials
import googleapiclient.discovery

Thêm tuyến vào máy chủ của bạn. Tạo một thực thể khác của google_auth_oauthlib.flow.Flow, nhưng lần này hãy sử dụng lại trạng thái đã lưu ở bước trước.

@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)

Tiếp theo, hãy yêu cầu quyền truy cập và làm mới mã thông báo. May mắn là đối tượng flow cũng chứa phương thức fetch_token để thực hiện việc này. Phương thức này dự kiến sẽ có các đối số code hoặc authorization_response. Hãy sử dụng authorization_response, vì đây là URL đầy đủ trong yêu cầu.

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

Bạn hiện đã có thông tin đăng nhập hoàn chỉnh! Lưu trữ các mã này trong phiên để có thể truy xuất các mã đó trong các phương thức hoặc tuyến đường khác, sau đó chuyển hướng đến một trang đích của tiện ích bổ sung.

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

Thêm một phương thức vào lớp dịch vụ để trả về đối tượng Credentials bằng cách truyền mã uỷ quyền được truy xuất từ lệnh chuyển hướng do URL uỷ quyền thực hiện. Sau đó, đối tượng Credentials này được dùng để truy xuất mã truy cập và mã làm mới.

/** 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;
    }
}

Thêm điểm cuối cho URI chuyển hướng của bạn đến bộ điều khiển. Truy xuất mã uỷ quyền và tham số state từ yêu cầu. So sánh tham số state này với thuộc tính state được lưu trữ trong phiên. Nếu các hình ảnh khớp với nhau, hãy tiếp tục quy trình uỷ quyền. Nếu không khớp, hãy trả về lỗi.

Sau đó, hãy gọi phương thức AuthService getAndSaveCredentials và truyền mã uỷ quyền dưới dạng tham số. Sau khi truy xuất đối tượng Credentials, hãy lưu trữ đối tượng đó trong phiên. Sau đó, hãy đóng hộp thoại và chuyển hướng người dùng đến trang đích của tiện ích bổ sung.

/** 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);
    }
}

Kiểm thử lệnh gọi API

Khi quy trình này hoàn tất, giờ đây bạn có thể thực hiện lệnh gọi đến các API của Google!

Ví dụ: yêu cầu thông tin hồ sơ của người dùng. Bạn có thể yêu cầu thông tin người dùng từ API OAuth 2.0.

Python

Đọc tài liệu về API khám phá OAuth 2.0 Sử dụng tài liệu này để nhận đối tượng UserInfo đã điền sẵn.

# 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

Tạo một phương thức trong lớp dịch vụ sẽ tạo đối tượng UserInfo bằng cách sử dụng Credentials làm tham số.

/** 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;
    }
}

Thêm điểm cuối /test vào bộ điều khiển hiển thị email của người dùng.

/** 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);
    }
}

Xóa thông tin xác thực

Bạn có thể "xoá" thông tin xác thực của người dùng bằng cách xoá họ khỏi phiên hiện tại. Điều này cho phép bạn kiểm tra việc định tuyến trên trang đích của tiện ích bổ sung.

Bạn nên hiển thị chỉ báo cho biết người dùng đã đăng xuất trước khi chuyển hướng họ đến trang đích của tiện ích bổ sung. Ứng dụng của bạn phải trải qua quy trình uỷ quyền để lấy thông tin xác thực mới, nhưng người dùng sẽ không được nhắc uỷ quyền lại cho ứng dụng.

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")

Ngoài ra, bạn có thể sử dụng flask.session.clear(). Tuy nhiên, việc này có thể mang lại hiệu quả không mong muốn nếu bạn có các giá trị khác được lưu trữ trong phiên.

Java

Trong trình điều khiển, hãy thêm một điểm cuối /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);
    }
}

Thu hồi quyền của ứng dụng

Người dùng có thể thu hồi quyền của ứng dụng bằng cách gửi yêu cầu POST đến https://oauth2.googleapis.com/revoke. Yêu cầu phải chứa mã truy cập của người dùng.

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

Thêm một phương thức vào lớp dịch vụ để thực hiện lệnh gọi đến điểm cuối thu hồi.

/** 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;
    }
}

Thêm một điểm cuối /revoke vào bộ điều khiển để xoá phiên đó và chuyển hướng người dùng đến trang uỷ quyền nếu quá trình thu hồi thành công.

/** 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);
    }
}

Kiểm thử tiện ích bổ sung

Đăng nhập vào Google Lớp học với tư cách là một trong những người dùng kiểm tra cho Giáo viên. Chuyển đến thẻ Bài tập trên lớp rồi tạo một Bài tập mới. Nhấp vào nút Tiện ích bổ sung bên dưới vùng văn bản rồi chọn tiện ích bổ sung của bạn. Iframe sẽ mở ra và tiện ích bổ sung sẽ tải URI thiết lập tệp đính kèm mà bạn đã chỉ định trên trang Cấu hình ứng dụng của SDK GWM.

Xin chúc mừng! Bạn đã sẵn sàng để chuyển sang bước tiếp theo: xử lý các lượt truy cập lặp lại vào tiện ích bổ sung của bạn.