Il s'agit du deuxième tutoriel concernant les modules complémentaires Classroom cette série de tutoriels.
Dans ce tutoriel, vous allez ajouter Google Sign-in à l'application Web. Il s'agit d'un comportement requis pour les modules complémentaires Classroom. Utiliser les identifiants fournis par ce flux d'autorisation pour tous les futurs appels de l'API.
Dans ce tutoriel, vous allez:
- Configurez votre application Web pour gérer les données de session dans un iFrame.
- Implémenter le flux de connexion de serveur à serveur de Google OAuth 2.0
- Envoyez un appel à l'API OAuth 2.0.
- Créer des routes supplémentaires pour permettre l'autorisation, la déconnexion et les tests Appels d'API.
Une fois l'opération terminée, vous pouvez autoriser complètement les utilisateurs dans votre application Web et émettre des appels vers aux API Google.
Comprendre le flux d'autorisation
Les API Google utilisent le protocole OAuth 2.0 pour l'authentification et l'autorisation. La description complète de la mise en œuvre OAuth de Google est disponible dans la Guide OAuth Google Identity
Les identifiants de votre application sont gérés dans Google Cloud. Une fois qu'ils ont sont créées, mettez en œuvre un processus en quatre étapes pour authentifier utilisateur:
- Demandez l'autorisation. Fournissez une URL de rappel dans cette requête. Une fois l'opération terminée, vous recevez une URL d'autorisation.
- Redirigez l'utilisateur vers l'URL d'autorisation. La page qui en résulte informe utilisateur des autorisations requises par votre application et l'invite à accorder l'accès. Une fois l'opération terminée, l'utilisateur est redirigé vers l'URL de rappel.
- Recevez un code d'autorisation sur votre itinéraire de rappel. Échangez le un code d'autorisation pour un jeton d'accès et un jeton d'actualisation.
- Appelez une API Google à l'aide des jetons.
Obtenir des identifiants OAuth 2.0
Assurez-vous d'avoir créé et téléchargé des identifiants OAuth comme décrit dans la section la page Vue d'ensemble. Votre projet doit utiliser ces identifiants pour connecter l'utilisateur.
Implémenter le flux d'autorisation
Ajoutez une logique et des routes à notre application Web pour réaliser le flux décrit, y compris ces fonctionnalités:
- Lancez la procédure d'autorisation lorsque vous atteignez la page de destination.
- Demandez l'autorisation et gérez la réponse du serveur d'autorisation.
- Effacez les identifiants stockés.
- Révoquez les autorisations de l'application.
- Tester un appel d'API
Lancer l'autorisation
Si nécessaire, modifiez votre page de destination pour lancer le flux d'autorisation. La peut avoir deux états possibles : soit des jetons sont enregistrés session en cours, ou vous devez obtenir des jetons auprès du serveur OAuth 2.0. Exécuter un appel d'API test s'il existe des jetons dans la session, ou demandez à l'utilisateur pour vous connecter.
Python
Ouvrez votre fichier routes.py
. Commencez par définir quelques constantes et notre cookie
conformément aux recommandations de sécurité concernant les cadres 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",
)
Accédez à l'itinéraire de destination du module complémentaire (/classroom-addon
dans l'exemple).
). Ajouter une logique pour afficher une page de connexion si la session ne contient pas
les "identifiants" .
@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
Le code de ce tutoriel est disponible dans le module step_02_sign_in
.
Ouvrez le fichier application.properties
et ajoutez une configuration de session qui
respecte les recommandations de sécurité concernant les cadres 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
Créer une classe de service (AuthService.java
dans le module step_02_sign_in
)
pour gérer la logique derrière les points de terminaison dans le fichier du contrôleur et configurer
l'URI de redirection, l'emplacement du fichier de secrets client et les champs d'application que votre module complémentaire
requiert. L'URI de redirection permet de rediriger les utilisateurs vers un URI spécifique.
après avoir autorisé votre application. Consultez la section Configuration du projet du
README.md
dans le code source pour savoir où placer les
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));
}
}
Ouvrez le fichier de la manette (AuthController.java
dans le fichier step_02_sign_in
).
) et d'ajouter une logique à l'itinéraire de destination pour afficher la page de connexion si le
session ne contient pas la clé 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);
}
}
Votre page d'autorisation doit contenir un lien ou un bouton permettant à l'utilisateur de "signer
". En cliquant dessus, l'utilisateur est redirigé vers l'itinéraire authorize
.
Autorisation de requête
Pour demander une autorisation, créez et redirigez l'utilisateur vers un URL. Cette URL inclut plusieurs informations, telles que les champs d'application demandée, la route de destination pour l'autorisation après l'autorisation et le paramètre ID client. Vous les trouverez dans cet exemple d'URL d'autorisation.
Python
Ajoutez l'importation suivante à votre fichier routes.py
.
import google_auth_oauthlib.flow
Créez une route /authorize
. Créer une instance de
google_auth_oauthlib.flow.Flow
; nous vous recommandons vivement d'utiliser
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)
Définissez le redirect_uri
de flow
. il s'agit de la route vers laquelle
vous souhaitez que les utilisateurs
à renvoyer après avoir autorisé votre application. Il s'agit de /callback
dans
à titre d'exemple.
# 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)
Utilisez l'objet Flow pour construire les éléments authorization_url
et state
. Magasin
le state
de la session ; permet de vérifier l'authenticité
la réponse du serveur. Enfin, redirigez l'utilisateur vers
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
Ajoutez les méthodes suivantes au fichier AuthService.java
pour instancier
flow, puis utilisez-le pour récupérer l'URL d'autorisation:
- La méthode
getClientSecrets()
lit le fichier secret du client et crée un objetGoogleClientSecrets
. - La méthode
getFlow()
crée une instance deGoogleAuthorizationCodeFlow
. - La méthode
authorize()
utilise l'objetGoogleAuthorizationCodeFlow
,state
et l'URI de redirection pour récupérer l'URL d'autorisation. Le paramètrestate
permet de vérifier l'authenticité de la réponse. à partir du serveur d'autorisation. La méthode renvoie ensuite une carte avec la l'URL d'autorisation et le paramètrestate
.
/** 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;
}
}
Utilisez l'injection par constructeur pour créer une instance de la classe de service dans le de la classe Contrôleur.
/** 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;
}
Ajoutez le point de terminaison /authorize
à la classe du contrôleur. Ce point de terminaison appelle
La méthode AuthService authorize()
pour récupérer le paramètre state
et l'URL d'autorisation. Ensuite, le point de terminaison stocke le state
dans la session et redirige les utilisateurs vers l'URL d'autorisation.
/** 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;
}
}
Gérer la réponse du serveur
Après l'autorisation, l'utilisateur revient à l'itinéraire redirect_uri
à partir de
à l'étape précédente. Dans l'exemple précédent, cet itinéraire est /callback
.
Vous recevez un code
dans la réponse lorsque l'utilisateur revient depuis le
page d'autorisation. Échangez ensuite le code contre des jetons d'accès et d'actualisation:
Python
Ajoutez les importations suivantes à votre fichier serveur Flask.
import google.oauth2.credentials
import googleapiclient.discovery
Ajoutez la route à votre serveur. Construisez une autre instance de
google_auth_oauthlib.flow.Flow
, mais réutilisez cette fois l'état enregistré dans
à l'étape précédente.
@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)
Demandez ensuite des jetons d'accès et d'actualisation. Heureusement, l'objet flow
contient la méthode fetch_token
pour y parvenir. La méthode attend
les arguments code
ou authorization_response
. Utilisez les
authorization_response
, car il s'agit de l'URL complète de la requête.
authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)
Vous avez désormais des identifiants complets ! Stockez-les dans la session afin qu'ils peuvent être récupérées via d'autres méthodes ou routes, puis sont redirigés vers un module complémentaire page de destination.
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
Ajoutez à votre classe de service une méthode qui renvoie l'objet Credentials
en
en transmettant le code d'autorisation récupéré à partir de la redirection effectuée par
l'URL d'autorisation. Cet objet Credentials
sera utilisé ultérieurement pour récupérer
le jeton d'accès et le jeton d'actualisation.
/** 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;
}
}
Ajoutez un point de terminaison pour votre URI de redirection au contrôleur. Récupérer les
et le paramètre state
de la requête. Comparer
le paramètre state
à l'attribut state
stocké dans la session. Si cette personne
avant de poursuivre le flux d'autorisation. Si ce n'est pas le cas,
renvoie une erreur.
Appelez ensuite la méthode getAndSaveCredentials
AuthService
et transmettez la valeur
le code d'autorisation en tant que paramètre. Après avoir récupéré la Credentials
, stockez-le dans la session. Fermez ensuite la boîte de dialogue et redirigez le
vers la page de destination du module complémentaire.
/** 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);
}
}
Tester un appel d'API
Une fois le flux terminé, vous pouvez émettre des appels vers les API Google.
Par exemple, demandez les informations de profil de l'utilisateur. Vous pouvez demander le les informations de l'utilisateur à partir de l'API OAuth 2.0.
Python
Lisez la documentation API de découverte OAuth 2.0 Utilisez-le pour obtenir un objet UserInfo renseigné.
# 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
Dans la classe de service, créez une méthode qui compile un objet UserInfo
à l'aide de
Credentials
comme paramètre.
/** 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;
}
}
Ajoutez le point de terminaison /test
au contrôleur qui affiche l'adresse e-mail de l'utilisateur.
/** 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);
}
}
Effacer les identifiants
Vous pouvez « effacer » les identifiants d'un utilisateur en le supprimant de la session en cours. Cela vous permet de tester l'itinéraire sur la page de destination du module complémentaire.
Nous vous recommandons d'indiquer que l'utilisateur s'est déjà déconnecté auparavant les rediriger vers la page de destination du module complémentaire. Votre application doit passer par flux d'autorisation pour obtenir de nouveaux identifiants, mais les utilisateurs ne sont pas invités à autoriser à nouveau votre application.
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")
Vous pouvez également utiliser flask.session.clear()
, mais cette action peut avoir des effets indésirables
si d'autres valeurs sont stockées dans la session.
Java
Dans le contrôleur, ajoutez un point de terminaison /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);
}
}
Révoquer l'autorisation de l'application
Un utilisateur peut révoquer l'autorisation de votre application en envoyant une requête POST
à
https://oauth2.googleapis.com/revoke
La requête doit contenir les informations
un jeton d'accès.
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
Ajoutez à la classe de service une méthode qui appelle le point de terminaison révoqué.
/** 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;
}
}
Ajoutez un point de terminaison, /revoke
, au contrôleur qui efface la session et
l'utilisateur est redirigé vers la page d'autorisation si la révocation a été
réussi.
/** 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);
}
}
Tester le module complémentaire
Connectez-vous à Google Classroom. en tant qu'utilisateur test Enseignant. Accédez à l'onglet Travaux et devoirs, puis créer un devoir. Cliquez sur le bouton Modules complémentaires sous la zone de texte, puis sélectionnez votre module complémentaire. L'iFrame s'ouvre et le module complémentaire charge URI de configuration de la pièce jointe que vous avez spécifié dans l'application du SDK GWM Configuration .
Félicitations ! Vous êtes prêt à passer à l'étape suivante: gérer la répétition les visites de votre module complémentaire.