Cómo devolver un JWT al iniciar sesión

Resumen

¿Cómo construir un endpoint de inicio de sesión que devuelva un JSON Web Token en Spring Security? Aquí aprendes a crear un AuthController que recibe credenciales, las valida con el AuthenticationManager y responde con un JWT en el encabezado Authorization. Es contenido pensado para desarrolladores backend que ya integraron la librería de JWT y quieren cerrar el flujo de autenticación.

¿Cómo funciona el flujo de autenticación con JWT en Spring?

El recorrido empieza cuando llega una authentication request al AuthController en su método login. Desde ahí, el controlador delega en el AuthenticationManager, que llama al DaoAuthenticationProvider, el cual a su vez consulta el UserDetailsService, en este caso el UserSecurityService que conecta con MySQL.

Cuando el AuthenticationProvider recibe el usuario desde la base de datos, compara la contraseña almacenada contra la que llegó en la petición. Si no coincide, responde con un 401. Si coincide, devuelve un 200 junto con el JWT que el cliente deberá enviar en el header Authorization en cada petición posterior [01:00].

¿Qué hace el AuthenticationManager en Spring Security? Coordina la autenticación: recibe un token con usuario y contraseña, lo pasa al provider correspondiente y devuelve un objeto Authentication si las credenciales son válidas.

¿Cómo se crea el AuthController y el LoginDTO?

Lo primero es crear un controlador anotado con @Controller y un @RequestMapping("/api/auth"). Dentro va un método login público que responde un ResponseEntity vacío y atiende peticiones POST sobre /login [02:00].

Para recibir credenciales necesitas un DTO plano. En la capa service/dto creas la clase LoginDTO, anotada con @Data, con dos atributos: username y password. Ese DTO entra al método marcado con @RequestBody.

java @Controller @RequestMapping("/api/auth") public class AuthController {

private final AuthenticationManager authenticationManager; private final JwtUtil jwtUtil; @Autowired public AuthController(AuthenticationManager authenticationManager, JwtUtil jwtUtil) { this.authenticationManager = authenticationManager; this.jwtUtil = jwtUtil; } @PostMapping("/login") public ResponseEntity<Void> login(@RequestBody LoginDTO loginDTO) { // ... }

}

¿Por qué declarar el AuthenticationManager como Bean?

Spring no inyecta el AuthenticationManager por defecto, así que en SecurityConfig defines un método público anotado con @Bean que recibe un AuthenticationConfiguration y retorna configuration.getAuthenticationManager() [03:30]. Sin esa anotación, la inyección con @Autowired falla porque el bean no existe en el contexto de Spring.

¿Cómo autenticar al usuario y devolver el JWT?

Dentro del método login construyes un UsernamePasswordAuthenticationToken con el username y password del DTO. Luego llamas a authenticationManager.authenticate(login) y guardas el resultado en un objeto Authentication del paquete org.springframework.security.core [04:30].

Si el flujo pasa esa línea, significa que el usuario quedó autenticado. Puedes verificarlo imprimiendo authentication.isAuthenticated() y authentication.getPrincipal() para ver el usuario dentro del contexto de seguridad.

Después creas el token con jwtUtil.create(loginDTO.getUsername()) y lo devuelves dentro del header Authorization:

java UsernamePasswordAuthenticationToken login = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());

Authentication authentication = this.authenticationManager.authenticate(login); System.out.println(authentication.isAuthenticated()); System.out.println(authentication.getPrincipal());

String jwt = jwtUtil.create(loginDTO.getUsername());

return ResponseEntity.ok() .header(HttpHeaders.AUTHORIZATION, jwt) .build();

¿Dónde viaja el JWT en la respuesta? En el header Authorization del response. El cuerpo queda vacío y el cliente debe leer el encabezado para guardarlo y reenviarlo en futuras peticiones.

¿Por qué el endpoint devuelve 401 la primera vez?

Al probar la petición POST /api/auth/login desde Postman con username: admin y password: admin123, la respuesta inicial es 401 Unauthorized. La razón es que el propio SecurityConfig está bloqueando la ruta antes de llegar al controlador.

La solución es agregar una regla en authorizeHttpRequests que permita el acceso libre a la ruta de login [07:00]:

  • Usa requestMatchers("/api/auth/**") como primera regla.
  • Aplica .permitAll() para que cualquier petición a ese path entre sin autenticación previa.
  • Mantén el resto de reglas debajo para proteger los demás endpoints.

Sin ese permiso, ningún usuario podría iniciar sesión, porque Spring Security exigiría un token que aún no existe.

¿Cómo confirmar que el flujo llegó al UserSecurityService?

Una técnica útil es implementar el método toString en la clase UserEntity y agregar un System.out.println dentro del UserSecurityService. Al lanzar la petición de login con credenciales válidas, en la consola de IntelliJ verás los atributos del UserEntity justo antes de que se imprima true, confirmando que el AuthenticationProvider consultó la base de datos antes de validar la contraseña.

Con credenciales correctas, la respuesta es 200 y el header Authorization trae el JWT. Con una contraseña inválida como 1234, la respuesta vuelve a 401 y no hay encabezado de autorización [09:30].

¿Ya probaste tu propio endpoint de login? Cuéntame en los comentarios qué cliente HTTP usas para inspeccionar el header Authorization.