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

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

Trong hướng dẫn từng bước 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 cho Google 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 hướng dẫn từng bước này, bạn cần hoàn thành các bước sau:

  • Định cấu hình ứng dụng web của bạn để 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ử các lệnh gọi API.

Sau khi hoàn tất, bạn có thể uỷ quyền hoàn toà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ề cách 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 tạo các quy trình này, hãy triển khai quy trình 4 bước để xác thực và uỷ quyền cho 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. Khi hoàn tất, bạn sẽ nhận được một URL ủy 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 sẽ được chuyển đến URL gọi lại.
  3. Nhận mã uỷ quyền theo phương thức gọi lại. Trao đổ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 API của 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 theo mô tả trên 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 người dùng.

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

Thêm logic và tuyến vào ứng dụng web để nhận ra quy trình được mô tả, bao gồm các tính năng sau:

  • Bắt đầu quy trình uỷ quyền khi 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ó 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. Hãy 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 của bạn. Trước tiên, hãy đặt một vài hằng số và cấu hình cookie theo đề xuất bảo mật của 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",
)

Chuyển đến tuyến đích của tiện ích bổ sung (trong tệp ví dụ là /classroom-addon). Thêm logic để hiển thị trang đăng nhập nếu phiên 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 đề xuất bảo mật của 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 trình điều khiển và thiết lập URI chuyển hướng, vị trí tệp bí mật của ứng dụng khách và các phạm vi mà tiện ích bổ sung của bạn yêu cầu. URI chuyển hướng được dùng để định tuyến lại người dùng của bạn đế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 của bạn.

@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". Khi nhấp vào đường liên kết này, người dùng sẽ được chuyển hướng đến tuyến authorize.

Yêu cầu uỷ quyền

Để yêu cầu uỷ 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 sau khi uỷ quyền và mã ứng dụng khách của ứng dụng web. Bạn có thể xem chúng trong URL uỷ quyền mẫu này.

Python

Thêm dữ liệu nhập sau vào tệp routes.py.

import google_auth_oauthlib.flow

Tạo 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 quay lại sau khi cho phép ứng dụng của bạn. Đây là /callback trong ví dụ sau.

# 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; mã này được 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 rồi sử dụng đối tượng đó để 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 bản sao 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 tệp ánh xạ 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 authorize() AuthService để truy xuất thông 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 cấp phép, người dùng quay lại tuyến redirect_uri từ bước trước. Trong ví dụ trước, 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 đó, hãy đổi mã đó lấy mã truy cập và làm mới mã:

Python

Thêm các nội dung nhập sau vào tệp máy chủ Flask của bạn.

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. Rất may 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 yêu cầu các đối số code hoặc authorization_response. 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ây giờ, bạn đã có thông tin đăng nhập hoàn chỉnh! Lưu trữ chúng trong phiên để có thể được truy xuất trong các phương thức hoặc tuyến đường khác, sau đó chuyển hướng đến 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 chuyển mã uỷ quyền truy xuất từ lệnh chuyển hướng do URL uỷ quyền thực hiện. Sau này, đối tượng Credentials này sẽ đượ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 của URI chuyển hướng vào trình đ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 mã khớp nhau, hãy tiếp tục quy trình uỷ quyền. Nếu chúng không khớp, hãy trả về lỗi.

Sau đó, hãy gọi phương thức AuthService getAndSaveCredentials và chuyển vào mã uỷ quyền dưới dạng thông số. Sau khi truy xuất đối tượng Credentials, hãy lưu trữ đối tượng đó trong phiên hoạt động. Sau đó, đó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

Sau khi hoàn tất quy trình này, bạn hiệ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 của người dùng qua API OAuth 2.0.

Python

Đọc tài liệu về API khám phá OAuth 2.0 Sử dụng API này để lấy đối tượng UserInfo được đ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ụ để 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 trình điều khiển có 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 đăng nhập của người dùng bằng cách xoá họ khỏi phiên hiện tại. Việc 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, hãy sử dụng flask.session.clear(), nhưng việc này có thể gây ra những kết 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 bộ đ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 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 đ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 thử Giáo viên. Chuyển đến thẻ Bài tập trên lớp và 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, sau đó 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.