Autenticar usuarios con Java

Esta parte del tutorial de Bookshelf para Java muestra cómo crear un flujo de inicio de sesión para los usuarios y cómo usar la información del perfil para ofrecerles funciones personalizadas.

Con Google Identity Platform, puedes acceder fácilmente a la información sobre los usuarios y al mismo tiempo garantizar que Google administre sus credenciales de inicio de sesión de forma segura. OAuth 2.0 facilita el flujo de inicio de sesión para todos los usuarios de su aplicación y facilita el acceso de tu aplicación a información básica de perfil sobre usuarios autenticados.

Esta página forma parte de un tutorial de varias páginas. Para empezar en orden y consultar las instrucciones de configuración, accede a la aplicación Bookshelf de Java.

Crear un ID de cliente de aplicación web

Un ID de cliente de aplicación web permite que la aplicación autorice a los usuarios y acceda a las API de Google en nombre de los usuarios.

  1. Accede a la sección de credenciales en la consola de Google Cloud Platform.

  2. Haz clic en Pantalla de autorización de OAuth. Introduce Java Bookshelf App en el nombre del producto. Completa los campos opcionales relevantes. Haz clic en Guardar.

  3. Haz clic en Crear credenciales > ID de cliente de OAuth.

  4. En Tipo de aplicación, selecciona Web.

  5. En Nombre, introduce Java Bookshelf Client.

  6. En URIs de redirección autorizados, introduce las siguientes URL una a una. Sustituye [YOUR_PROJECT_ID] por el ID del proyecto:

    http://localhost:8080/oauth2callback
    http://[YOUR_PROJECT_ID].appspot.com/oauth2callback
    https://[YOUR_PROJECT_ID].appspot.com/oauth2callback
    http://[YOUR_PROJECT_ID].appspot-preview.com/oauth2callback
    https://[YOUR_PROJECT_ID].appspot-preview.com/oauth2callback

  7. Haz clic en Crear.

  8. Copia el ID de cliente y el secreto de cliente y guárdalos para usarlos más adelante.

Establecer configuración

1. En el directorio getting-started-java/bookshelf/4-auth, abre pom.xml para editarlo.

  1. En la sección <properties>, configura callback.host en [YOUR_PROJECT_ID].appspot.com.

  2. Define bookshelf.clientID en el ID de cliente que has creado.

  3. Configura bookshelf.clientSecret en el secreto de cliente que has creado.

  4. Guarda y cierra pom.xml.

Ejecutar la aplicación en la máquina local

Para ejecutar la aplicación de forma local, sigue estas instrucciones:

  1. En el directorio getting-started-java/bookshelf/4-auth, introduce el siguiente comando para iniciar un servidor web local:

    mvn -Plocal clean jetty:run-exploded -DprojectID=[YOUR-PROJECT-ID]
  2. En el navegador web, accede a http://localhost:8080.

Ahora puedes navegar por las páginas web de la aplicación, iniciar sesión con tu cuenta de Google, añadir libros y ver los libros que agregaste mediante el enlace Mis libros que aparece en la barra de navegación superior.

Desplegar la aplicación en el entorno flexible de App Engine

  1. Introduce este comando para desplegar la aplicación:

    mvn appengine:deploy -DprojectID=YOUR-PROJECT-ID
    
  2. En el navegador web, introduce la siguiente dirección. Sustituye [YOUR_PROJECT_ID] por el ID del proyecto:

    https://[YOUR_PROJECT_ID].appspot-preview.com
    

Si actualizas la aplicación, podrás desplegar la versión actualizada con el mismo comando utilizado para desplegar la aplicación por primera vez. El nuevo despliegue crea una nueva versión de la aplicación y la establece como la versión predeterminada. Se conservan las versiones anteriores de la aplicación, al igual que las instancias de máquina virtual asociadas. Ten en cuenta que todas estas versiones de la aplicación y las instancias de máquina virtual son recursos facturables.

Si eliminas las versiones no predeterminadas de la aplicación, puedes reducir los costes.

Para eliminar una versión de la aplicación, sigue las instrucciones que figuran a continuación:

  1. In the GCP Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

Si quieres obtener información completa sobre cómo limpiar los recursos facturables, consulta la sección de limpieza en el último paso de este tutorial.

Estructura de la aplicación

En el siguiente diagrama, se muestran los componentes de la aplicación y cómo se conectan entre ellos.

Estructura de muestra de Auth

Comprender el código

En esta sección se explica detalladamente el código de la aplicación y su funcionamiento.

El LoginServlet se invoca cuando el usuario hace clic en Iniciar sesión. Hace lo siguiente:

  1. Guarda un state aleatorio para ayudar a evitar falsificaciones de peticiones.

  2. Guarda el destino de dónde ir después de iniciar sesión.

  3. Utiliza la biblioteca cliente de la API de Google, en concreto, GoogleAuthorizationCodeFlow, para generar una petición de devolución de llamada a Google para gestionar el inicio de sesión en una cuenta de Google. Esta aplicación especifica los ámbitos "correo electrónico" y "perfil", por lo que puede mostrar el correo electrónico y la imagen del usuario en cada página:

@WebServlet(name = "login", value = "/login")
@SuppressWarnings("serial")
public class LoginServlet extends HttpServlet {

  private static final Collection<String> SCOPES = Arrays.asList("email", "profile");
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  private GoogleAuthorizationCodeFlow flow;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {

    String state = new BigInteger(130, new SecureRandom()).toString(32);  // prevent request forgery
    req.getSession().setAttribute("state", state);

    if (req.getAttribute("loginDestination") != null) {
      req
          .getSession()
          .setAttribute("loginDestination", (String) req.getAttribute("loginDestination"));
    } else {
      req.getSession().setAttribute("loginDestination", "/books");
    }

    flow = new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY,
        getServletContext().getInitParameter("bookshelf.clientID"),
        getServletContext().getInitParameter("bookshelf.clientSecret"),
        SCOPES)
        .build();

    // Callback url should be the one registered in Google Developers Console
    String url =
        flow.newAuthorizationUrl()
            .setRedirectUri(getServletContext().getInitParameter("bookshelf.callback"))
            .setState(state)            // Prevent request forgery
            .build();
    resp.sendRedirect(url);
  }
}

Google redirige al usuario a la URL /oauth2callback. Después de que el usuario inicie sesión, el método Oauth2CallbackServlet doGet hace lo siguiente:

  1. Comprobar que no es una falsificación mediante la comparación de nuestro state con el state de la sesión guardada.

  2. Eliminar el state de la sesión guardada.

  3. Obtener la respuesta tokenResponse.

  4. Usar tokenResponse para obtener una Credential.

  5. Usar la credential para crear una requestFactory.

  6. Usar la request para obtener la jsonIdentity.

  7. Extraer el correo electrónico, una imagen y una identificación.

  8. Redirigir al loginDestination guardado desde el paso anterior.

@WebServlet(name = "oauth2callback", value = "/oauth2callback")
@SuppressWarnings("serial")
public class Oauth2CallbackServlet extends HttpServlet {

  private static final Collection<String> SCOPES = Arrays.asList("email", "profile");
  private static final String USERINFO_ENDPOINT
      = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  private GoogleAuthorizationCodeFlow flow;

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
      ServletException {

    // Ensure that this is no request forgery going on, and that the user
    // sending us this connect request is the user that was supposed to.
    if (req.getSession().getAttribute("state") == null
        || !req.getParameter("state").equals((String) req.getSession().getAttribute("state"))) {
      resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
      resp.sendRedirect("/books");
      return;
    }

    req.getSession().removeAttribute("state");     // Remove one-time use state.

    flow = new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY,
        getServletContext().getInitParameter("bookshelf.clientID"),
        getServletContext().getInitParameter("bookshelf.clientSecret"),
        SCOPES).build();

    final TokenResponse tokenResponse =
        flow.newTokenRequest(req.getParameter("code"))
            .setRedirectUri(getServletContext().getInitParameter("bookshelf.callback"))
            .execute();

    req.getSession().setAttribute("token", tokenResponse.toString()); // Keep track of the token.
    final Credential credential = flow.createAndStoreCredential(tokenResponse, null);
    final HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(credential);

    final GenericUrl url = new GenericUrl(USERINFO_ENDPOINT);      // Make an authenticated request.
    final HttpRequest request = requestFactory.buildGetRequest(url);
    request.getHeaders().setContentType("application/json");

    final String jsonIdentity = request.execute().parseAsString();
    @SuppressWarnings("unchecked")
    HashMap<String, String> userIdResult =
        new ObjectMapper().readValue(jsonIdentity, HashMap.class);
    // From this map, extract the relevant profile info and store it in the session.
    req.getSession().setAttribute("userEmail", userIdResult.get("email"));
    req.getSession().setAttribute("userId", userIdResult.get("sub"));
    req.getSession().setAttribute("userImageUrl", userIdResult.get("picture"));
    resp.sendRedirect((String) req.getSession().getAttribute("loginDestination"));
  }
}

El LogoutServlet elimina la session y crea una nueva:

@WebServlet(name = "logout", value = "/logout")
@SuppressWarnings("serial")
public class LogoutServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    // you can also make an authenticated request to logout, but here we choose to
    // simply delete the session variables for simplicity
    HttpSession session =  req.getSession(false);
    if (session != null) {
      session.invalidate();
    }
    // rebuild session
    req.getSession();
  }
}
¿Te ha resultado útil esta página? Enviar comentarios: