CodeGym /Cursos /Módulo 5. Spring /Lección 104: Configuración de la autenticación con JWT

Lección 104: Configuración de la autenticación con JWT

Módulo 5. Spring
Nivel 18 , Lección 3
Disponible

En esta lección veremos:

  1. Cómo configurar Spring Boot para trabajar con JWT.
  2. La implementación de la generación de tokens JWT tras una autenticación exitosa.
  3. Configurar un filtro de seguridad para validar tokens en las peticiones.
  4. 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.

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