En esta lección veremos:
- Cómo configurar Spring Boot para trabajar con JWT.
- La implementación de la generación de tokens JWT tras una autenticación exitosa.
- Configurar un filtro de seguridad para validar tokens en las peticiones.
- Gestionar el acceso a recursos protegidos con JWT y Spring Security.
¿Listos? Preparad un café y ¡vamos!
Configuración de Spring Boot para trabajar con JWT
Para empezar crearemos un proyecto base de Spring Boot. Asegúrate de que tu pom.xml o build.gradle incluye las dependencias necesarias:
Dependencias
Si usas Maven, añade esto a tu pom.xml:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Biblioteca JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
O, si usas Gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
}
Estas dependencias proporcionarán las herramientas necesarias para implementar la autenticación JWT.
Generación de tokens JWT
Vamos a generar tokens JWT. Crea la clase JwtTokenUtil, que se encargará de crear y validar tokens.
Implementación de JwtTokenUtil
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTokenUtil {
private static final String SECRET_KEY = "secret"; // Clave secreta (¡no uses claves tan simples en producción!)
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 horas
// Generación del token
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>(); // Aquí puedes añadir datos personalizados
return Jwts.builder()
.setClaims(claims)
.setSubject(username) // Usamos el nombre de usuario como subject del token
.setIssuedAt(new Date()) // Fecha de emisión del token
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // Fecha de expiración
.signWith(SignatureAlgorithm.HS512, SECRET_KEY) // Usamos el algoritmo HMAC-SHA512 para firmar
.compact();
}
// Validación del token (comprobamos si ha expirado y si coincide el usuario)
public boolean validateToken(String token, String username) {
final String tokenUsername = extractUsername(token);
return (username.equals(tokenUsername) && !isTokenExpired(token));
}
// Extracción del nombre de usuario desde el token
public String extractUsername(String token) {
return extractAllClaims(token).getSubject();
}
// Comprobar si el token ha expirado
public boolean isTokenExpired(String token) {
return extractAllClaims(token).getExpiration().before(new Date());
}
// Extracción de todos los claims (payload del token)
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
}
En esta clase hemos implementado los métodos básicos para trabajar con JWT. Ahora podemos generar tokens, validarlos y extraer datos del payload.
Protección de recursos con JWT
Ahora pasamos a la configuración de seguridad. Usaremos Spring Security para filtrar las peticiones y comprobar los tokens.
Configuración del filtro de seguridad
Añadiremos un filtro que intercepte las peticiones, extraiga el JWT del header y lo valide.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
public JwtRequestFilter(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
// Extraemos el token del header "Authorization: Bearer ..."
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.extractUsername(jwt);
}
// Comprobamos el token y establecemos el contexto de seguridad
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(jwt, username)) {
// Crear autenticación basada en el token (ejemplo simplificado)
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, new ArrayList<>()
);
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
// Dejamos pasar la petición
chain.doFilter(request, response);
}
}
Este filtro comprobará el token en cada petición y añadirá la info del usuario al contexto de seguridad.
Configuración de Spring Security
Crearemos la clase de configuración de seguridad para registrar nuestro JwtRequestFilter y configurar el acceso a recursos protegidos.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/authenticate").permitAll() // Acceso público para la autenticación
.anyRequest().authenticated() // El resto de peticiones requieren autenticación
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Implementación del endpoint de autenticación
Crearemos un controlador que recibirá el nombre de usuario y la contraseña, y devolverá un JWT tras una autenticación exitosa.
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthController {
private final JwtTokenUtil jwtTokenUtil;
public AuthController(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@PostMapping("/authenticate")
public String authenticate(@RequestBody AuthRequest authRequest) {
// Comprobación simple del usuario (para demo; en producción usa UserDetailsService)
if ("user".equals(authRequest.getUsername()) && "password".equals(authRequest.getPassword())) {
return jwtTokenUtil.generateToken(authRequest.getUsername());
} else {
throw new RuntimeException("Credenciales inválidas");
}
}
}
// DTO para la petición de autenticación
class AuthRequest {
private String username;
private String password;
// Getters y Setters
}
Ahora, enviando un POST a /authenticate con los datos correctos, recibirás un JWT que puedes usar para acceder a recursos protegidos.
En este punto tenemos una aplicación básica con autenticación JWT. Hemos creado una estructura que se escala fácilmente para proyectos más complejos. En la práctica, usa bases de datos para almacenar usuarios y roles, y añade más capas de seguridad.
GO TO FULL VERSION