Implementación de un Interceptor Personalizado para Control de Sesión en Spring Boot

Configuración del Módulo de Interceptores

Para gestionar el ciclo de vida de las peticiones HTTP y aplicar lógica transversal como la validación de credenciales, Spring Boot expone la interfaz HandlerInterceptor. Este componente permite inyectar lógica antes (preHandle), durante o después del procesamiento de un manejador, sin acoplarla directamente a los controladores.

1. Definición del Filtro de Autenticación

Se crea una clase que implementa HandlerInterceptor. El método preHandle evalúa si existe un identificador activo en la sesión. En caso contrario, almacena un mensaje de advertencia en la solicitud y fuerza una redirección interna hacia el endpoint de acceso público, retornando false para cnacelar la propagación.

package com.ejemplo.autenticacion.interceptores;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class VerificacionSesion implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest solicitud, HttpServletResponse respuesta, Object manejador) throws Exception {
        System.out.println("Validando ruta solicitada: " + solicitud.getRequestURI());

        String contextoUsuario = (String) solicitud.getSession().getAttribute("tokenActivo");
        
        if (contextoUsuario == null || contextoUsuario.isBlank()) {
            solicitud.setAttribute("notificacionAcceso", "Debe autenticarse para utilizar la aplicación.");
            solicitud.getRequestDispatcher("/acceso").forward(solicitud, respuesta);
            return false;
        }
        return true;
    }
}

2. Registro en el Ciclo de Vida MVC

La configuración global se centraliza en una clase marcada con @Configuration que implementa WebMvcConfigurer. Se sobrescriben dos métodos clave:

  • addViewControllers: Mapea directamente la raíz del contexto a la plantilla de inicio de sesión.
  • addInterceptors: Registra el filtro personalizado, definiendo los patrones de ruta a proteger y aquellos que deben permanecer accesibles (inicio, formularios y recursos estáticos).
package com.ejemplo.autenticacion.config;

import com.ejemplo.autenticacion.interceptores.VerificacionSesion;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ConfiguracionRutas implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registro) {
        registro.addViewController("/").setViewName("formulario-login");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registro) {
        registro.addInterceptor(new VerificacionSesion())
                .addPathPatterns("/api/**", "/panel/**")
                .excludePathPatterns("/", "/acceso", "/css/**", "/js/**", "/img/**");
    }
}

3. Lógica del Controlador y Gestión de Estado

El controlador encapapsula las rutas públicas y protegidas. La validación compara las credenciales ingresadas contra un conjunto predefinido. Al superarse, se inyecta el atributo tokenActivo en la sesión y se realiza una redirección hacia el panel principal. Para terminar el ciclo, se llama a invalidate() sobre el objeto HttpSession.

package com.ejemplo.autenticacion.controladores;

import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Controller
public class GestorAplicacion {

    private static final List<string> LISTA_USUARIOS = Arrays.asList("admin", "guest", "editor");
    private static final String CONTRASENA_MANDATORIA = "demo_pass";

    @PostMapping("/acceso")
    public String ejecutarLogin(@RequestParam String identificativo, 
                                @RequestParam String contrasenia, 
                                HttpSession sesion, 
                                Model modelo,
                                RedirectAttributes redirect) {
        
        if (!StringUtils.hasText(identificativo) || !StringUtils.hasText(contrasenia)) {
            redirect.addAttribute("error", "Los campos son obligatorios.");
            return "redirect:/acceso";
        }

        if (LISTA_USUARIOS.contains(identificativo) && CONTRASENA_MANDATORIA.equals(contrasenia)) {
            sesion.setAttribute("tokenActivo", identificativo);
            sesion.setMaxInactiveInterval(3600);
            return "redirect:/panel";
        }

        redirect.addAttribute("error", "Identificador o contraseña incorrectos.");
        return "redirect:/acceso";
    }

    @GetMapping("/panel")
    public String mostrarDashboard(Model modelo) {
        List<object> registros = new ArrayList<>();
        for (int i = 1; i <= 4; i++) {
            registros.add(new Object[]{i, "Registro Sistema " + i});
        }
        modelo.addAttribute("tablaDatos", registros);
        return "vista-principal";
    }

    @GetMapping("/salir")
    public String cerrarContexto(HttpSession sesion) {
        sesion.invalidate();
        return "redirect:/";
    }
}</object></string>

4. Plantillas Thymeleaf

Las vistas renderizan formularios POST y tablas dinámicas. Se utiliza el namespace th para vincular modelos y acciones HTTP.


<html lang="es" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Portal de Acceso</title>
</head>
<body>
    <form th:action="@{/acceso}" method="post">
        <p th:if="${error}" th:text="${error}" style="color:red;"></p>
        <div>
            <label>Identificador:</label>
            <input type="text" name="identificativo" required />
        </div>
        <div>
            <label>Contraseña:</label>
            <input type="password" name="contrasenia" required />
        </div>
        <button type="submit">Ingresar al Sistema</button>
    </form>
</body>
</html>

<html lang="es" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Panel Operativo</title>
</head>
<body>
    <h2>Registros Disponibles</h2>
    <table border="1">
        <tr><th>ID</th><th>Nombre</th></tr>
        <tr th:each="fila : ${tablaDatos}">
            <td th:text="${fila[0]}"></td>
            <td th:text="${fila[1]}"></td>
        </tr>
    </table>
    
    <a href="/salir">Terminar Sesión</a>
</body>
</html>

Este esquema arquitectónico separa claramente la validación transversal de la lógica de negocio. El interceptor actúa como puerta de entrada, mientras que los controladores gestionan la persistencia temporal del estado mediante HttpSession. Las plantillas interpretan los objetos modelados y permiten la navgeación segura entre recursos restringidos y puntos públicos.

Etiquetas: spring-boot HandlerInterceptor WebMvcConfigurer Gestión-de-Sesiones Thymeleaf

Publicado el 10-20 16:07