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

Đây là phần hướng dẫn thứ hai trong loạt hướng dẫn về các tiện ích bổ sung của 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 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 tham khảo hướng dẫn này, bạn sẽ hoàn thành những việc sau:

  • Định cấu hình ứng dụng web để duy trì dữ liệu phiên trong một 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 gọi đế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 đầy đủ cho người dùng trong ứng dụng web 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 xác thực của ứng dụng được quản lý trong Google Cloud. Sau khi tạo các đối tượng này, hãy triển khai quy trình gồm 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 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ọ cho phép truy cập. Khi hoàn tất, người dùng sẽ được chuyển hướng đến URL gọi lại.
  3. Nhận mã uỷ quyền tại tuyến 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 một API của Google bằng mã thông báo.

Lấy thông tin xác thực OAuth 2.0

Đảm bảo rằng bạn đã tạo và tải thông tin xác thực OAuth xuống như mô tả trong trang Tổng quan. Dự án của bạn phải sử dụng các thông tin xác thực 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 của chúng ta để hiện thực hoá 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 người dùng 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 xác thực đã 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 để 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. 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 theo các đề xuất bảo mật về 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 trang đí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 không chứa khoá "credentials".

@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 này trong mô-đun step_02_sign_in.

Mở tệp application.properties rồi thêm cấu hình phiên theo các đề xuất bảo mật về 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 đằng 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 đế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 trình điều khiển (AuthController.java trong mô-đun step_02_sign_in) và thêm logic vào tuyến đích để hiển thị 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 nú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ư các phạm vi được yêu cầu, tuyến đích cho sau khi uỷ quyền và mã ứng dụng 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 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 dự định người dùng sẽ quay lại sau khi uỷ quyền cho ứ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 flow để tạo authorization_urlstate. Lưu trữ state trong phiên; giá trị này được dùng để xác minh tính xác thực của phản hồi 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 bản sao đối tượng luồng, sau đó sử dụng đối tượng đó để truy xuất URL uỷ quyền:

  • Phương thức getClientSecrets() đọc tệp bí mật của ứng dụng và tạo một đố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, thông số state và URI chuyển hướng để truy xuất URL uỷ quyền. Tham số state được 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 trình đ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 sẽ 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 uỷ quyền, người dùng sẽ 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 code trong phản hồi khi người dùng quay lại từ trang uỷ quyền. Sau đó, hãy trao đổi mã này để lấy mã truy cập và mã làm mới:

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 đường vào máy chủ. Tạo một thực thể khác của google_auth_oauthlib.flow.Flow, nhưng lần nà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 yêu cầu đối số code hoặc authorization_response. Sử dụng authorization_response vì đây là URL đầy đủ của yêu cầu.

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

Giờ đây, bạn đã có thông tin xác thực đầy đủ! Lưu trữ các giá trị này trong phiên để có thể truy xuất trong các phương thức hoặc tuyến 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 truyền vào mã uỷ quyền được truy xuất từ lệnh chuyển hướng do URL uỷ quyền thực hiện. Đối tượng Credentials này sẽ được dùng sau để truy xuất mã thông báo truy cập và mã thông báo 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 vào 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 khớp, 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 vào làm 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 hoàn tất, 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 của 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 API này để lấy đố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ụ để 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á thông tin đó khỏi phiên hiện tại. Thao tác này cho phép bạn kiểm thử việc định tuyến trên trang đích của tiện ích bổ sung.

Bạn nên hiển thị thông 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 các hiệu ứng ngoài 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ã thông báo 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 bằng vai trò là một trong những người dùng thử nghiệm 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 khu vực văn bản, sau đó chọn tiện ích bổ sung. 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 trong 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.