Logowanie użytkownika

To drugi przewodnik z serii o dodatkach do Classroom.

W tym samouczku dowiesz się, jak dodać logowanie Google do aplikacji internetowej. Jest to wymagane zachowanie w przypadku dodatków do Classroom. Użyj danych logowania z tego procesu autoryzacji we wszystkich przyszłych wywołaniach interfejsu API.

W ramach tego samouczka wykonasz te czynności:

  • Skonfiguruj aplikację internetową, aby przechowywać dane sesji w ramce iframe.
  • Wdrożyć proces logowania serwera do serwera z użyciem protokołu Google OAuth 2.0.
  • Wywołaj interfejs OAuth 2.0 API.
  • Tworzenie dodatkowych ścieżek do obsługi autoryzacji, wylogowywania i testowania wywołań interfejsu API.

Po zakończeniu procesu możesz w pełni autoryzować użytkowników w swojej aplikacji internetowej i wywoływać interfejsy API Google.

Proces autoryzacji

Interfejsy API Google używają protokołu OAuth 2.0 do uwierzytelniania i autoryzacji. Pełny opis implementacji OAuth w Google znajdziesz w przewodniku OAuth w Google Identity.

Dane logowania do aplikacji są zarządzane w Google Cloud. Po ich utworzeniu zastosuj 4-etapowy proces uwierzytelniania i autoryzacji użytkownika:

  1. Poproś o autoryzację. W ramach tego zgłoszenia podaj adres URL wywołania zwrotnego. Po zakończeniu procesu otrzymasz URL autoryzacji.
  2. Przekieruj użytkownika do adresu URL autoryzacji. Na tej stronie użytkownik dowiaduje się, jakich uprawnień wymaga aplikacja, i jest proszony o przyznanie dostępu. Po zakończeniu procesu użytkownik zostanie przekierowany na adres URL wywołania zwrotnego.
  3. Otrzymaj kod autoryzacji w ramach wywołania zwrotnego. Wymień kod autoryzacji na token dostępu i token odświeżania.
  4. Wykonywanie wywołań interfejsu Google API przy użyciu tokenów.

Uzyskiwanie danych logowania OAuth 2.0

Upewnij się, że utworzyłeś(-aś) i pobrano dane logowania OAuth zgodnie z opisem na stronie Ogółem. Twój projekt musi używać tych danych logowania do logowania użytkownika.

Wdrażanie procesu autoryzacji

Dodaj do naszej aplikacji internetowej logikę i trasy, aby zrealizować opisany proces, w tym:

  • Rozpocznij proces autoryzacji po przejściu na stronę docelową.
  • Poproś o autoryzację i obsługuj odpowiedź serwera autoryzacji.
  • Wyczyść przechowywane dane logowania.
  • anulować uprawnienia aplikacji;
  • przetestować wywołanie interfejsu API.

Rozpoczęcie autoryzacji

W razie potrzeby zmodyfikuj stronę docelową, aby zainicjować proces autoryzacji. Dodatek może znajdować się w 2 stanach: albo w bieżącej sesji są zapisane tokeny, albo musisz uzyskać tokeny z serwera OAuth 2.0. Wykonaj testowe wywołanie interfejsu API, jeśli w sesji są tokeny, lub poproś użytkownika o zalogowanie się.

Python

Otwórz plik routes.py. Najpierw ustaw kilka stałych wartości i konfigurację plików cookie zgodnie z zaleceniami dotyczącymi bezpieczeństwa 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",
)

Przejdź do ścieżki docelowej dodatku (w pliku przykładowym jest to /classroom-addon). Dodaj logikę, aby renderować stronę logowania, jeśli sesja nie zawiera klucza „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

Kod potrzebny do wykonania tej procedury znajdziesz w module step_02_sign_in.

Otwórz plik application.properties i dodaj konfigurację sesji zgodną z zaleceniami dotyczącymi bezpieczeństwa 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

Utwórz klasę usługi (AuthService.java w module step_02_sign_in), aby obsłużyć logikę punktów końcowych w pliku kontrolera, a także skonfiguruj URI przekierowania, lokalizację pliku z tajemnicami klienta i zakresy, których wymaga dodatek. Identyfikator URI przekierowania służy do przekierowywania użytkowników do określonego identyfikatora URI po autoryzacji aplikacji. Informacje o miejscu umieszczenia pliku client_secret.json znajdziesz w sekcji Konfigurowanie projektu w README.md w źródle kodu.

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

Otwórz plik kontrolera (AuthController.java w module step_02_sign_in) i dodaj do trasy docelowej logikę, aby renderować stronę logowania, jeśli sesja nie zawiera klucza 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);
    }
}

Strona autoryzacji powinna zawierać link lub przycisk, który użytkownik może kliknąć, aby się zalogować. Po kliknięciu tego przycisku użytkownik powinien zostać przekierowany na trasę authorize.

Poproś o autoryzację

Aby poprosić o autoryzację, utwórz adres URL uwierzytelniający i przekieruj do niego użytkownika. Adres URL zawiera kilka informacji, takich jak żądane zakresy, ścieżka docelowa po autoryzacji oraz identyfikator klienta aplikacji internetowej. Możesz je zobaczyć w tym przykładowym adresie URL autoryzacji.

Python

Dodaj do pliku routes.py te instrukcje importu.

import google_auth_oauthlib.flow

Utwórz nową trasę /authorize. Utwórz instancję klasy google_auth_oauthlib.flow.Flow. Zdecydowanie zalecamy użycie do tego celu dołączonej metody 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)

Ustaw flow redirect_uri. To ścieżka, do której użytkownicy mają wracać po autoryzowaniu aplikacji. W tym przykładzie jest to /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)

Użyj obiektu przepływu, aby utworzyć obiekty authorization_urlstate. Zapisz state w sesji. Służy on do późniejszego sprawdzania autentyczności odpowiedzi serwera. Na koniec przekieruj użytkownika do 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

Aby utworzyć instancję obiektu przepływu, a następnie użyć go do pobrania adresu URL autoryzacji, dodaj do pliku AuthService.java te metody:

  • Metoda getClientSecrets() odczytuje plik z tajnymi kluczami klienta i utworzy obiekt GoogleClientSecrets.
  • Metoda getFlow() tworzy instancję GoogleAuthorizationCodeFlow.
  • Metoda authorize() używa obiektu GoogleAuthorizationCodeFlow, parametru state i identyfikatora URI przekierowania, aby pobrać adres URL autoryzacji. Parametr state służy do weryfikacji autentyczności odpowiedzi ze serwera autoryzacji. Następnie zwraca mapę z adresem URL autoryzacji i parametrem 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;
    }
}

Aby utworzyć instancję klasy usługi w klasie kontrolera, użyj iniekcji konstruktora.

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

Dodaj punkt końcowy /authorize do klasy kontrolera. Ten punkt końcowy wywołuje metodę AuthService authorize(), aby pobrać parametr state i adres URL autoryzacji. Następnie punkt końcowy przechowuje parametr state w sesji i przekierowuje użytkowników na adres URL autoryzacji.

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

Obsługa odpowiedzi serwera

Po autoryzacji użytkownik wraca do ścieżki redirect_uri z poprzedniego kroku. W poprzednim przykładzie jest to /callback.

Gdy użytkownik wróci ze strony autoryzacji, otrzymasz odpowiedź z wartością code. Następnie wymień kod na tokeny dostępu i odświeżania:

Python

Dodaj do pliku serwera Flask te instrukcje importu:

import google.oauth2.credentials
import googleapiclient.discovery

Dodaj trasę do serwera. Utwórz kolejną instancję funkcji google_auth_oauthlib.flow.Flow, ale tym razem użyj stanu zapisanego w poprzednim kroku.

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

Następnie poproś o tokeny dostępu i odświeżania. Na szczęście obiekt flow zawiera też metodę fetch_token, która umożliwia wykonanie tej operacji. Metoda oczekuje argumentów code lub authorization_response. Użyj adresu authorization_response, ponieważ jest to pełny adres URL z żądania.

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

Masz już pełne dane logowania. Przechowuj je w sesji, aby można je było pobrać w inne sposoby lub ścieżki, a potem przekieruj na stronę docelową dodatku.

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

Dodaj do klasy usługi metodę, która zwraca obiekt Credentials, przekazując kod autoryzacji pobrany z adresu URL autoryzacji. Ten obiekt Credentials jest później używany do pobierania tokena dostępu i tokena odświeżania.

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

Dodaj punkt końcowy dla identyfikatora URI przekierowania do kontrolera. Pobierz z żądania kod autoryzacji i parametr state. Porównaj ten parametr state z atrybutem state zapisanym w sesji. Jeśli się zgadza, kontynuuj proces autoryzacji. Jeśli nie zgadzają się, zwracany jest błąd.

Następnie wywołaj metodę AuthService getAndSaveCredentials i przekaż kod autoryzacji jako parametr. Po pobraniu obiektu Credentials zapisz go w sesji. Następnie zamknij okno i przekieruj użytkownika na stronę docelową dodatku.

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

Testowanie wywołania interfejsu API

Po zakończeniu procesu możesz już wykonywać wywołania interfejsów API Google.

Możesz na przykład poprosić o informacje z profilu użytkownika. Możesz poprosić o informacje o użytkowniku z interfejsu API OAuth 2.0.

Python

Zapoznaj się z dokumentacją interfejsu API do wyszukiwania informacji o użytkowniku w ramach protokołu OAuth 2.0. Użyj go, aby uzyskać wypełniony obiekt 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

W klasie usługi utwórz metodę, która tworzy obiekt UserInfo, używając jako parametru obiektu 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;
    }
}

Dodaj punkt końcowy /test do kontrolera, który wyświetla adres e-mail użytkownika.

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

Wyczyść dane o certyfikatach

Możesz „wyczyścić” poświadczenia użytkownika, usuwając je z bieżącej sesji. Dzięki temu możesz przetestować przekierowywanie na stronie docelowej dodatku.

Zalecamy wyświetlenie informacji o wylogowaniu użytkownika, zanim przekierujesz go na stronę docelową dodatku. Aplikacja powinna przejść proces autoryzacji, aby uzyskać nowe dane logowania, ale użytkownicy nie są proszeni o ponowne autoryzowanie aplikacji.

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

Możesz też użyć funkcji flask.session.clear(), ale może to mieć niezamierzone skutki, jeśli masz inne wartości zapisane w sesji.

Java

W kontrolerze dodaj punkt końcowy /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);
    }
}

Anulowanie uprawnień aplikacji

Użytkownik może cofnąć uprawnienia aplikacji, wysyłając POST do https://oauth2.googleapis.com/revoke. Prośba powinna zawierać token dostępu użytkownika.

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

Dodaj do klasy usługi metodę, która wywołuje punkt końcowy anulowania.

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

Dodaj do kontrolera punkt końcowy /revoke, który czyści sesję i przekierowuje użytkownika do strony autoryzacji, jeśli odstąpienie od umowy zostało zaakceptowane.

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

Testowanie dodatku

Zaloguj się w Google Classroom jako jeden z nauczycieli użytkowników testowych. Otwórz kartę Zadania i utwórz nowe projekty. Kliknij przycisk Dodatki pod obszarem tekstowym, a następnie wybierz dodatek. Otworzy się iframe, a dodatek wczyta URI konfiguracji załącznika, który został podany na stronie Konfiguracja aplikacji w GWM SDK.

Gratulacje! Możesz przejść do następnego kroku: obsługiwanie powtarzających się wizyt w Twoim dodatku.