Access Token (token de acceso) — es una clave que permite al usuario interactuar con recursos protegidos. Sin embargo, por seguridad, su tiempo de vida está limitado. Por ejemplo, un Access Token puede ser válido solo durante 15 minutos. Tras expirar el token, todas las peticiones protegidas empezarán a devolver el error 401 Unauthorized. En ese caso el usuario se ve obligado a autenticarse de nuevo, y eso claramente le molestará (y dudo que vaya a poner 5 estrellas a tu app en el App Store).
Solución: Refresh tokens
El Refresh token está pensado para renovar un Access Token caducado sin forzar al usuario a volver a introducir sus credenciales. Esto hace que la interacción del usuario con la app sea más fluida y el sistema más cómodo.
¿Cómo funciona?
- Al autenticar con éxito, el servidor emite dos tokens:
Access Token— para acceder a recursos protegidos.Refresh Token— para obtener un nuevo Access Token cuando el anterior haya caducado.
- Cuando expira el Access Token, el cliente (por ejemplo, tu app móvil o web) envía el Refresh Token al servidor.
- El servidor verifica el Refresh Token y, si es válido, emite un nuevo Access Token.
- El usuario sigue usando la app sin tener que autenticarse de nuevo.
Configuración y uso de Refresh tokens en Spring
Vamos a crear una aplicación en Spring Boot que use Refresh tokens para renovar el Access Token. Para esto necesitas una app existente con autenticación JWT. Si hiciste los ejercicios de las lecciones anteriores, ya tienes una configuración básica de JWT. Si no, te recomiendo retroceder un poco y hacerlo primero.
1. Generación de Refresh tokens
Empezamos cambiando la lógica de emisión de tokens. Ahora nuestro endpoint de autenticación devolverá dos tokens.
Ejemplo de código para generar tokens:
@RestController
@RequestMapping("/auth")
public class AuthController {
private final JwtService jwtService; // Servicio para trabajar con JWT
private final UserService userService;
@PostMapping("/login")
public ResponseEntity
login(@RequestBody LoginRequest request) {
// Verificamos las credenciales del usuario
User user = userService.authenticate(request.getUsername(), request.getPassword());
// Generamos Access y Refresh tokens
String accessToken = jwtService.generateAccessToken(user);
String refreshToken = jwtService.generateRefreshToken(user);
// Los devolvemos en la respuesta
return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken
));
}
}
Ten en cuenta que ahora usamos dos métodos distintos para generar Access y Refresh tokens. El Access Token normalmente tiene un tiempo de vida corto (por ejemplo, 15 minutos), mientras que el Refresh Token es más largo (por ejemplo, 7 días).
2. Añadir endpoint para refrescar tokens
Ahora añadiremos un endpoint que acepta el Refresh Token y devuelve un nuevo Access Token.
Ejemplo de implementación:
@RestController
@RequestMapping("/auth")
public class AuthController {
private final JwtService jwtService;
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody Map<String, String> body) {
String refreshToken = body.get("refreshToken");
// Comprobamos el Refresh token
if (!jwtService.isValidRefreshToken(refreshToken)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
}
// Generamos un nuevo Access Token
String newAccessToken = jwtService.generateAccessTokenFromRefreshToken(refreshToken);
return ResponseEntity.ok(Map.of(
"accessToken", newAccessToken
));
}
}
3. Lógica de verificación y generación de tokens en JwtService
Añadiremos dos nuevos métodos en JwtService. Uno para validar el Refresh token y otro para generar un nuevo Access Token a partir de él.
Ejemplo:
@Service
public class JwtService {
private final String secretKey = "your-secret-key"; // Clave secreta para firmar los tokens
public boolean isValidRefreshToken(String refreshToken) {
try {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(refreshToken)
.getBody();
// Asegúrate de que esto es realmente un Refresh token
String tokenType = claims.get("type", String.class);
return "refresh".equals(tokenType);
} catch (JwtException e) {
return false;
}
}
public String generateAccessTokenFromRefreshToken(String refreshToken) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(refreshToken)
.getBody();
String username = claims.getSubject();
// Creamos un nuevo Access Token
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15 minutos
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
}
Aquí es importante asegurarse de que el Refresh token realmente sea un Refresh token (por ejemplo, por un campo adicional "type" en el payload).
4. Manejo seguro de los Refresh tokens
Los Refresh tokens son más sensibles porque tienen una vida más larga. Aquí van algunas recomendaciones para mejorar la seguridad:
- Almacenamiento en cliente: nunca guardes los Refresh tokens en
localStorage. Usa cookies HTTP-only. - Protocolo HTTPS: siempre usa una conexión segura al transmitir los tokens.
- Rotación de tokens: tras usar un Refresh token para renovar el Access Token, puedes generar un nuevo Refresh token y devolvérselo al cliente. Esto reduce la probabilidad de compromiso.
- Lista de tokens revocados: en caso de detectar una fuga, debes poder revocar Refresh tokens. Normalmente se hace usando una base de datos para rastrear los tokens válidos.
5. Manejo de errores
Problemas típicos con los Refresh tokens:
- Token caducado: devuelve
401 Unauthorizedy pide al usuario que se autentique de nuevo. - Token inválido: si el token está corrupto, recházalo.
- Falsificación de token: usa firma digital (HMAC o RSA) para verificar la autenticidad.
Ejemplo de manejo de errores:
if (!jwtService.isValidRefreshToken(refreshToken)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
}
Aplicación práctica
Hoy en día es difícil imaginar una app moderna sin Refresh tokens. Por ejemplo:
- En aplicaciones móviles, donde es importante mantener al usuario logueado durante mucho tiempo.
- En aplicaciones web que necesitan soportar solicitudes en segundo plano (por ejemplo, obtener nuevas entradas del feed).
- Los empleadores valoran el conocimiento sobre Refresh tokens, ya que demuestra comprensión de principios de seguridad, experiencia de usuario y trabajo con tokens.
La documentación oficial de Spring Security sobre JWT y OAuth2 está disponible aquí.
Ahora estás listo para usar Refresh tokens en tus aplicaciones y puedes mejorar significativamente la experiencia de usuario y la seguridad.
GO TO FULL VERSION