Logowanie użytkownika

To drugi przewodnik z serii instrukcji dodatków do Classroom.

W tym przewodniku dodasz Logowanie przez Google do aplikacji internetowej. Jest to wymagane w przypadku dodatków do Classroom. Używaj danych logowania z tego procesu autoryzacji we wszystkich przyszłych wywołaniach interfejsu API.

W ramach tego przewodnika wykonasz następujące czynności:

  • Skonfiguruj aplikację internetową, by przechowywać dane sesji w elemencie iframe.
  • Zaimplementuj proces logowania się między serwerami Google OAuth 2.0.
  • Wywołaj interfejs API OAuth 2.0.
  • Utwórz dodatkowe trasy do autoryzowania, wylogowywania się i testowania wywołań interfejsu API.

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

Omówienie procesu autoryzacji

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

Dane logowania do aplikacji są zarządzane w Google Cloud. Gdy to zrobisz, zastosuj 4-etapowy proces autoryzacji i uwierzytelniania użytkownika:

  1. Poproś o autoryzację. Podaj w nim adres URL wywołania zwrotnego. Po zakończeniu otrzymasz adres URL autoryzacji.
  2. Przekieruj użytkownika na adres URL autoryzacji. Na wyświetlonej stronie użytkownik jest informowany o uprawnieniach wymaganych przez aplikację oraz z prośbą o przyznanie dostępu. Po zakończeniu użytkownik zostanie przekierowany pod adres URL wywołania zwrotnego.
  3. Odbieraj kod autoryzacji na trasie wywołania zwrotnego. Wymień kod autoryzacji na token dostępu i token odświeżania.
  4. Za pomocą tokenów możesz wywołać interfejs API Google.

Uzyskiwanie danych logowania OAuth 2.0

Sprawdź, czy masz utworzone i pobrane dane logowania OAuth zgodnie z opisem na stronie Przegląd. Twój projekt musi używać tych danych logowania do zalogowania użytkownika.

Wdrażanie procesu autoryzacji

Dodaj do naszej aplikacji internetowej funkcje logiczne i trasy, aby umożliwić realizację opisanego powyżej procesu, w tym te funkcje:

  • Rozpocznij proces autoryzacji, gdy dotrzesz na stronę docelową.
  • Żądanie autoryzacji i obsługę odpowiedzi serwera autoryzacji.
  • Wyczyść zapisane dane logowania.
  • Cofnij uprawnienia aplikacji.
  • przetestować wywołanie interfejsu API,

Rozpocznij autoryzację

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

Python

Otwórz plik routes.py. Najpierw ustaw kilka stałych oraz konfigurację plików cookie zgodnie z zaleceniami dotyczącymi zabezpieczeń 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 trasy docelowej dodatku (jest to /classroom-addon w przykładowym pliku). Dodaj logikę, aby renderować stronę logowania, jeśli sesja nie zawiera klucza „danych logowania”.

@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 tego przewodnika znajdziesz w module step_02_sign_in.

Otwórz plik application.properties i dodaj konfigurację sesji zgodną z zaleceniami dotyczącymi zabezpieczeń 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ługiwać logikę stojącą za punktami końcowymi w pliku kontrolera oraz skonfigurować identyfikator URI przekierowania, lokalizację pliku z tajnymi kluczami klienta i zakresy, których wymaga dodatek. Identyfikator URI przekierowania służy do przekierowywania użytkowników do określonego identyfikatora URI po ich autoryzowaniu aplikacji. Informacje o tym, gdzie umieścić plik client_secret.json, znajdziesz w sekcji dotyczącej konfigurowania projektu w README.md w kodzie źródłowym.

@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 funkcje logiczne do trasy docelowej, 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 umożliwiający użytkownikowi zalogowanie się. Kliknięcie go powinno przekierować użytkownika na trasę authorize.

Poproś o autoryzację

Aby zażądać autoryzacji, utwórz i przekieruj użytkownika na adres URL uwierzytelniania. Zawiera on kilka informacji, takich jak żądane zakresy, trasa docelowa po autoryzacji oraz identyfikator klienta aplikacji internetowej. Znajdziesz je na tym przykładowym adresie URL autoryzacji.

Python

Dodaj poniższy import do pliku routes.py.

import google_auth_oauthlib.flow

Utwórz nową trasę /authorize. Utwórz instancję google_auth_oauthlib.flow.Flow. Zdecydowanie zalecamy użycie do tego uwzględnionej 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 redirect_uri obiektu flow. Jest to trasa, na którą chcesz wrócić po autoryzacji aplikacji. W przykładzie poniżej 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 do utworzenia authorization_url i state. Zapisz state w sesji. Służy do późniejszego sprawdzania autentyczności odpowiedzi serwera. Na koniec przekieruj użytkownika do witryny 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

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

  • Metoda getClientSecrets() odczytuje plik tajnego klienta i tworzy obiekt GoogleClientSecrets.
  • Metoda getFlow() tworzy wystąpienie GoogleAuthorizationCodeFlow.
  • Metoda authorize() pobiera adres URL autoryzacji za pomocą obiektu GoogleAuthorizationCodeFlow, parametru state oraz identyfikatora URI przekierowania. Parametr state służy do sprawdzania autentyczności odpowiedzi z serwera autoryzacji. Metoda 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;
    }
}

Użyj wstrzykiwania przez konstruktor, aby utworzyć instancję klasy usługi w klasie kontrolera.

/** 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 do adresu 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 na trasę redirect_uri z poprzedniego kroku. W powyższym przykładzie ta trasa to /callback.

Gdy użytkownik wraca ze strony autoryzacji, w odpowiedzi otrzymasz code. Następnie wymień kod na tokeny dostępu i odświeżania:

Python

Dodaj te importy do pliku serwera Flask.

import google.oauth2.credentials
import googleapiclient.discovery

Dodaj trasę do serwera. Utwórz kolejne wystąpienie obiektu 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 dostęp i odśwież tokeny. Na szczęście obiekt flow zawiera też metodę fetch_token, która służy do tego celu. Metoda wymaga argumentu code lub authorization_response. Użyj właściwości authorization_response, ponieważ jest to pełny adres URL z żądania.

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

Masz teraz pełne dane logowania. Przechowuj je w sesji, aby można było je pobierać za pomocą innych metod lub tras, a następnie przekierowują 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 przekierowania wykonanego przez adres URL autoryzacji. Ten obiekt Credentials będzie 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 identyfikatora URI przekierowania do kontrolera. Pobierz z żądania kod autoryzacji i parametr state. Porównaj ten parametr state z atrybutem state przechowywanym w sesji. Jeśli pasują, kontynuuj proces autoryzacji. Jeśli nie są zgodne, zwraca komunikat o błędzie.

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 tego procesu możesz wysyłać wywołania do interfejsów API Google.

Możesz na przykład poprosić o informacje o profilu użytkownika. Dostęp do informacji o użytkowniku możesz uzyskać za pomocą interfejsu OAuth 2.0 API.

Python

Zapoznaj się z dokumentacją interfejsu API wykrywania 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

Utwórz w klasie usługi metodę, która buduje obiekt UserInfo z użyciem parametru 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

Dane logowania użytkownika można „wyczyścić”, usuwając je z bieżącej sesji. Pozwoli Ci to przetestować routing na stronie docelowej dodatku.

Zalecamy pokazanie, że użytkownik się wylogował, zanim zostanie przekierowany na stronę docelową dodatku. Aplikacja powinna przejść proces autoryzacji, aby uzyskać nowe dane logowania, ale użytkownicy nie będą proszeni o ponowną autoryzację.

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ć polecenia flask.session.clear(), ale jeśli w sesji masz zapisane inne wartości, może to przynieść niezamierzone efekty.

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

Cofnij uprawnienia aplikacji

Użytkownik może cofnąć uprawnienia aplikacji, wysyłając żądanie POST do https://oauth2.googleapis.com/revoke. Żądanie powinno 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 unieważnienia.

/** 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 punkt końcowy /revoke do kontrolera, który czyści sesję i przekierowuje użytkownika na stronę autoryzacji, jeśli unieważnienie się powiodło.

/** 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 użytkowników testowych Nauczyciela. Otwórz kartę Zadania i utwórz nowy projekt. Kliknij przycisk Dodatki pod obszarem tekstowym, a następnie wybierz dodatek. Otworzy się element iframe i dodatek wczyta identyfikator URI konfiguracji załączników podany na stronie Konfiguracja aplikacji pakietu GWM SDK.

Gratulacje! Możesz teraz przejść do kolejnego kroku, czyli obsługiwania wielokrotnych wizyt w dodatku.