CodeGym /Cursos /Módulo 5. Spring /Gestión de sesiones y tokens

Gestión de sesiones y tokens

Módulo 5. Spring
Nivel 17 , Lección 8
Disponible

Cuando estudias aplicaciones web seguro te has encontrado con el término "sesión". Una sesión es un mecanismo que permite al servidor "recordar" al usuario entre peticiones. Esto es crucial para la autorización: si no, tendrías que introducir usuario y contraseña en cada clic.

¿Cómo funciona una sesión? Cuando el usuario se autentica, el servidor crea un identificador único (Session ID). Ese identificador se envía al cliente (por ejemplo, el navegador) normalmente en forma de cookie. En cada petición posterior el cliente adjunta ese identificador, lo que permite al servidor saber quién es.


Gestión de sesiones en Spring Security

Spring Security incluye una funcionalidad potente para trabajar con sesiones. Veamos cómo configurarlas y gestionarlas.

Configuración de la política de sesiones

Una de las primeras cosas con las que te encontrarás es configurar la política de gestión de sesiones. Por ejemplo, puedes definir cuántas sesiones activas puede tener un usuario. Para eso puedes usar los métodos sessionManagement() y maximumSessions().

Ejemplo:


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement()
                .maximumSessions(1) // Sólo una sesión por usuario
                .maxSessionsPreventsLogin(true); // Evita el inicio de sesión al superar el límite
    }
}

Aquí limitamos al usuario a una sesión activa. Si intenta entrar desde otro dispositivo, se le denegará el acceso.

Gestión del cierre de sesión

Spring Security permite configurar la lógica que se ejecuta cuando una sesión termina (por ejemplo, si la sesión expira o se cierra manualmente). Para eso puedes usar la interfaz SessionRegistry.

Un handler para el cierre de sesión podría verse así:


@Component
public class CustomLogoutHandler implements LogoutHandler {

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        System.out.println("Sesión terminada: " + authentication.getName());
    }
}

Almacenamiento de sesiones

Las sesiones pueden almacenarse en la memoria del servidor o en una base de datos. El uso de la base de datos es común en sistemas distribuidos, donde el balanceador de carga puede dirigir al usuario a diferentes servidores entre peticiones. En esos casos las sesiones deben ser "compartidas".


Tokens de seguridad: ¿por qué las sesiones no son suficientes?

En la era de los microservicios y las REST API, la idea de los tokens cobra protagonismo. Los tokens son cadenas compactas que representan cierta información (por ejemplo, el identificador de usuario). A diferencia de las sesiones, los tokens no requieren almacenamiento en el servidor.

Ventajas de los tokens

  1. Independencia del estado del servidor: los tokens se almacenan en el cliente, lo que facilita escalar la aplicación.
  2. Menor carga: el servidor no tiene que mantener estado por cada usuario.
  3. Comodidad para APIs: los tokens se pueden enviar en la cabecera de la petición HTTP, lo cual es ideal para REST API.

JWT (JSON Web Token)

JWT es un formato popular de tokens que consiste en una cadena JSON codificada. Cada token tiene tres partes:

  1. Header (cabecera): tipo de token y algoritmo de cifrado usado.
  2. Payload (carga útil): datos del token, por ejemplo userId o role.
  3. Signature (firma): protege el token contra falsificación.

Ejemplo de token JWT:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiVVNFUiJ9.abc123signature

Uso de JWT en Spring Security

Vamos a añadir JWT a la seguridad de nuestra aplicación.

Para generar tokens necesitarás una librería, por ejemplo, io.jsonwebtoken (JWT). Aquí tienes un ejemplo de creación de token:


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtTokenUtil {

    private static final String SECRET_KEY = "mySecretKey";

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 día
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

Validación de tokens

Antes de usar un token es importante verificar que es auténtico y que no ha expirado. Para eso hay que decodificarlo y comprobar la firma:


import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;

public class JwtTokenUtil {

    private static final String SECRET_KEY = "mySecretKey";

    public Claims validateToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }
}

El método validateToken() devuelve el payload del token si es válido.


Configuración de la autenticación JWT

Ahora integremos JWT en Spring Security. Para ello creamos un filtro:


@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7); // Quitamos "Bearer "
            Claims claims = jwtTokenUtil.validateToken(token);
            if (claims != null) {
                String username = claims.getSubject();
                // Creamos el objeto de autenticación
                UsernamePasswordAuthenticationToken auth =
                        new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        filterChain.doFilter(request, response);
    }
}

El filtro verifica el token antes de que la petición continúe.


Ejemplo de uso de tokens en nuestra aplicación

Hemos creado un REST API para autenticar. En el controlador /login se devuelve un token JWT. El cliente debe enviar ese token en la cabecera Authorization en peticiones posteriores.

Controlador:


@RestController
public class AuthController {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestParam String username, @RequestParam String password) {
        // Aquí normalmente compruebas username/password
        String token = jwtTokenUtil.generateToken(username);
        return ResponseEntity.ok(new HashMap<String, String>() {{
            put("token", token);
        }});
    }
}

Ahora tu cliente puede hacer una petición autorizada:


curl -H "Authorization: Bearer <your_token>" http://localhost:8080/protected/resource

¿Cuándo usar sesiones y cuándo tokens?

Si trabajas con aplicaciones web tradicionales, las sesiones siguen siendo una gran opción. Son simples, cómodas y están bien integradas en el ecosistema Spring.

Sin embargo, para REST API y microservicios las sesiones no son tan cómodas. Aquí destacan los tokens, especialmente los JWT, que ofrecen movilidad, facilidad de escalado e independencia del estado del servidor.


Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION