Esta es la segunda explicación en los complementos de Classroom .
En esta explicación, agregarás el Acceso con Google a la aplicación web. Este es un el comportamiento requerido de los complementos de Classroom. Usa las credenciales de este flujo de autorización para todas las llamadas futuras a la API.
En esta explicación, completaste lo siguiente:
- Configura tu aplicación web para que mantenga los datos de las sesiones en un iframe.
- Implementar el flujo de acceso de servidor a servidor de Google OAuth 2.0
- Realiza una llamada a la API de OAuth 2.0.
- Crea rutas adicionales para admitir la autorización, el cierre de sesión y las pruebas Llamadas a la API.
Una vez finalizado, puedes autorizar completamente a los usuarios en tu aplicación web y emitir llamadas a APIs de Google.
Comprende el flujo de autorización
Las APIs de Google usan el protocolo OAuth 2.0 para la autenticación y la autorización. Puedes encontrar la descripción completa de la implementación de OAuth de Google en el Guía de OAuth de Google Identity.
Las credenciales de tu aplicación se administran en Google Cloud. Una vez que tengan un proceso de cuatro pasos para autenticar y autorizar usuario:
- Autorización de solicitud Proporciona una URL de devolución de llamada como parte de esta solicitud. Cuando se complete, recibirás una URL de autorización.
- Redireccionar al usuario a la URL de autorización La página resultante informa al usuario de los permisos que requiere tu app y le solicita que permita el acceso. Cuando se completa, se dirige al usuario a la URL de devolución de llamada.
- Recibir un código de autorización en tu ruta de devolución de llamada Intercambia el código de autorización de un token de acceso y un token de actualización.
- Realiza llamadas a una API de Google usando los tokens.
Obtén credenciales de OAuth 2.0
Asegúrate de haber creado y descargado las credenciales de OAuth como se describe en la página Resumen. Tu proyecto debe usar estas credenciales para que el usuario acceda.
Cómo implementar el flujo de autorización
Agregar lógica y rutas a nuestra aplicación web para obtener el flujo descrito, lo que incluye estas funciones:
- Inicia el flujo de autorización cuando llegues a la página de destino.
- Solicita autorización y controla la respuesta del servidor de autorización.
- Borra las credenciales almacenadas.
- Revocar los permisos de la app
- Probar una llamada a la API
Iniciar autorización
Si es necesario, modifica la página de destino para iniciar el flujo de autorización. El el complemento puede tener dos estados posibles: ya sea que haya tokens guardados en el sesión actual, o bien debes obtener tokens del servidor de OAuth 2.0. Realizar una llamada a la API de prueba si hay tokens en la sesión o pedirle al usuario para acceder.
Python
Abre el archivo routes.py
. Primero, establezca algunas constantes
según las recomendaciones de seguridad de 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",
)
Desplázate a la ruta de destino del complemento (en el ejemplo, esta es /classroom-addon
)
. Agrega lógica para procesar una página de acceso si la sesión no contiene
las “credenciales” .
@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
El código para esta explicación se puede encontrar en el módulo step_02_sign_in
.
Abre el archivo application.properties
y agrega la configuración de la sesión que
sigue las recomendaciones de seguridad de 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
Crea una clase de servicio (AuthService.java
en el módulo step_02_sign_in
).
para manejar la lógica detrás de los endpoints en el archivo del controlador y configurar
el URI de redireccionamiento, la ubicación del archivo de secretos del cliente y los alcances
la infraestructura y los procesos
que requiere la administración de datos. El URI de redireccionamiento se usa para redirigir a los usuarios a un URI específico
después de que autoricen tu app. Consulta la sección Configuración del proyecto del
README.md
en el código fuente para obtener información sobre dónde colocar el
client_secret.json
archivo.
@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));
}
}
Abre el archivo del controlador (AuthController.java
en step_02_sign_in
módulo) y agrega lógica a la ruta de destino para renderizar la página de acceso si el
la sesión no contiene la clave 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);
}
}
Tu página de autorización debe contener un vínculo o un botón para que el usuario “firme”
in”. Al hacer clic en este botón, se debería redireccionar al usuario a la ruta authorize
.
Solicitar autorización
Para solicitar autorización, crea y redirecciona al usuario a una autenticación URL. Esta URL incluye varios datos, como los alcances solicitada, la ruta de destino para la autorización after y el espacio de nombres ID de cliente. Puedes verlas en esta URL de autorización de muestra.
Python
Agrega la siguiente importación a tu archivo routes.py
.
import google_auth_oauthlib.flow
Crea una nueva ruta /authorize
. Crea una instancia de
google_auth_oauthlib.flow.Flow
; te recomendamos que uses los atributos
from_client_secrets_file
para hacerlo.
@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)
Establece el redirect_uri
de flow
. esta es la ruta a la que pretendes que los usuarios
que regrese después de autorizar su aplicación. Se muestra /callback
en el siguiente
ejemplo.
# 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)
Usa el objeto de flujo para construir authorization_url
y state
. Tienda
el state
en la sesión; se usa para verificar la autenticidad de la
respuesta del servidor más tarde. Por último, redirecciona al usuario a la
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
Agrega los siguientes métodos al archivo AuthService.java
para crear una instancia de la
Flow y, luego, úsalo para recuperar la URL de autorización:
- El método
getClientSecrets()
lee el archivo de secretos del cliente y construye Un objetoGoogleClientSecrets
- El método
getFlow()
crea una instancia deGoogleAuthorizationCodeFlow
. - El método
authorize()
usa el objetoGoogleAuthorizationCodeFlow
, elstate
y el URI de redireccionamiento para recuperar la URL de autorización. El parámetrostate
se usa para verificar la autenticidad de la respuesta. del servidor de autorización. Luego, el método devuelve un mapa con el y el parámetrostate
.
/** 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;
}
}
Usa la inyección de constructor para crear una instancia de la clase de servicio en la controlador.
/** 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;
}
Agrega el extremo /authorize
a la clase del controlador. Este extremo llama
el método authorize()
de AuthService para recuperar el parámetro state
y la URL de autorización. Luego, el extremo almacena state
parámetro de la sesión y redirecciona a los usuarios a la URL de autorizació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;
}
}
Controla la respuesta del servidor
Después de la autorización, el usuario regresa a la ruta redirect_uri
desde el
paso anterior. En el ejemplo anterior, esta ruta es /callback
.
Recibirás una code
en la respuesta cuando el usuario regrese del
página de autorización. Luego, intercambia el código por tokens de acceso y actualización:
Python
Agrega las siguientes importaciones al archivo del servidor de Flask.
import google.oauth2.credentials
import googleapiclient.discovery
Agrega la ruta a tu servidor. Construye otra instancia de
google_auth_oauthlib.flow.Flow
, pero esta vez vuelve a usar el estado guardado en
paso anterior.
@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)
A continuación, solicita acceso y tokens de actualización. Por suerte, el objeto flow
también
contiene el método fetch_token
para lograrlo. El método espera
los argumentos code
o authorization_response
. Usa el
authorization_response
, ya que es la URL completa de la solicitud.
authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)
Ya tienes las credenciales completas. Guárdalos en la sesión para que se puede recuperar en otros métodos o rutas y, luego, redireccionar a un complemento la página de destino.
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
Agrega un método a tu clase de servicio que muestre el objeto Credentials
. Para ello, haz lo siguiente:
y pasar el código de autorización recuperado del redireccionamiento que realizó
la URL de autorización. Este objeto Credentials
se usará más tarde para recuperar
el token de acceso y el token de actualización.
/** 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;
}
}
Agrega un extremo para tu URI de redireccionamiento al controlador. Recupera el
el código de autorización y el parámetro state
de la solicitud. Comparar esto
El parámetro state
al atributo state
almacenado en la sesión Si
y, luego, continúa con el flujo de autorización. Si no coinciden,
se muestra un error.
Luego, llama al método getAndSaveCredentials
de AuthService
y pasa el
el código de autorización como parámetro. Después de recuperar Credentials
de ese objeto, almacénalo en la sesión. Luego, cierra el cuadro de diálogo y redirecciona el
usuario a la página de destino del complemento.
/** 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);
}
}
Prueba una llamada a la API
Una vez completado el flujo, podrás emitir llamadas a las APIs de Google.
A modo de ejemplo, solicita la información de perfil del usuario. Puedes solicitar el la información del usuario desde la API de OAuth 2.0.
Python
Lee la documentación sobre API de descubrimiento de OAuth 2.0 Úsalo para obtener un objeto UserInfo propagado.
# 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
Crea un método en la clase de servicio que compile un objeto UserInfo
usando
Credentials
como parámetro.
/** 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;
}
}
Agrega el extremo /test
al controlador que muestra el correo electrónico del usuario.
/** 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);
}
}
Borrar credenciales
Puedes "borrar" las credenciales de un usuario quitándolo de la sesión actual. Esto te permite probar el enrutamiento en la página de destino del complemento.
Te recomendamos que muestres una indicación de que el usuario salió de la cuenta antes y redireccionarlos a la página de destino del complemento. Tu app debe pasar por de autorización para obtener credenciales nuevas, pero no se solicita a los usuarios que vuelve a autorizar tu app.
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")
También puedes usar flask.session.clear()
, pero es posible que esta acción haya sido involuntaria.
efectos si tienes otros valores almacenados en la sesión.
Java
En el controlador, agrega un extremo /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);
}
}
Revocar el permiso de la app
Un usuario puede revocar el permiso de tu app enviando una solicitud POST
a
https://oauth2.googleapis.com/revoke
La solicitud debe contener el nombre del usuario
token de acceso.
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
Agrega un método a la clase de servicio que realice una llamada al extremo de revocación.
/** 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;
}
}
Agregar un extremo, /revoke
, al controlador que borre la sesión y
redirecciona al usuario a la página de autorización si se realizó la revocación
y exitoso.
/** 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);
}
}
Prueba el complemento
Accede a Google Classroom. como uno de tus usuarios de prueba Teacher Navega a la pestaña Trabajo en clase y haz lo siguiente: Crea una Tarea nueva. Haz clic en el botón Complementos debajo del área de texto. y, luego, selecciona el complemento. Se abre el iframe y el complemento carga la URI de configuración de archivos adjuntos que especificaste en la app del SDK de GWM. Configuración .
¡Felicitaciones! Ahora puedes continuar con el siguiente paso: controlar la repetición visitas a tu complemento.