¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Yavé Guadaño Ibáñez Hace 1 día Cargando comentarios…
Hemos cogido carrerilla y ya vamos por la 12ª entrega de nuestra serie sobre patrones de arquitectura de microservicios. Como siempre, os animo a que echéis un vistazo a toda la serie de posts:
En el post anterior, hacíamos la introducción a los patrones de seguridad y nos adentramos en Token-based Authentication (Autenticación basada en tokens) y OAuth (Open Authorization). En este post continuamos con JWT.
Nota: Los ejemplos de código son únicamente para entender los conceptos. Puede haber partes incompletas o con trozos demostrativos que no aplicarían a entornos reales.
JSON Web Tokens (JWT) es un estándar (RFC 7519) para representar declaraciones (claims) de forma segura, usando JSON y firmas digitales. Un JWT típico consta de tres partes:
Header (Base64-url): {"alg":"HS256","typ":"JWT"}
Payload (Base64-url): {"sub":"5","email":"usuario@demo.com","roles":["ROLE_USER"],"exp":1700515039}
Firma (Base64-url): generada con HMAC-SHA256.
Una vez formado, un JWT puede verse así (simplificado):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiI1IiwiZW1haWwiOiJ1c3VhcmlvQGRlbW8uY29tIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImV4cCI6MTcwMDUxNTAzOX0
.
NLk__3FHxpb3ZzRdC0s5EZhWpTnzKnLxK4bEB-tkL28
Supongamos que nuestro e-commerce tiene un servicio de autenticación que, en vez de emitir tokens opacos y guardarlos en una base de datos, emite directamente JWT firmados.
El diagrama de secuencia:
El diagrama de flujo:
El proceso, es el mismo pero con algo más de detalle:
La gran ventaja: no necesitamos consultar al Auth Service para cada solicitud. La desventaja principal es que si queremos revocar un token antes de que expire, tenemos que implementar una estrategia adicional (lista negra, cambio de clave, intervalos de expiración muy cortos, etc.).
En este apartado vamos a ver cómo integrar JWT (JSON Web Tokens) en una aplicación Spring Boot paso a paso.
Dependencias
Para empezar, es fundamental agregar las dependencias correspondientes a Spring Security y la librería para manejar tokens JWT. Generalmente se incluyen:
Esto permite al proyecto tanto configurar la protección de endpoints con Spring Security como firmar y analizar (parsear) tokens JWT.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Clase para generar JWT (JwtTokenProvider)
En este punto, se crea una clase (por ejemplo, JwtTokenProvider) responsable de generar el token una vez que el usuario se haya autenticado correctamente.
Se suele usar un método generateToken(Authentication authentication) que:
El payload del token suele contener:
Esta clase también se encargará de:
@Service
public class JwtTokenProvider {
private final String jwtSecret = "MiSuperSecreto";
private final long jwtExpirationInMillis = 3600000; // 1 hora
// Crear JWT basado en datos de usuario
public String generateToken(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMillis);
// Ejemplo: claims: subject, roles, email, etc.
return Jwts.builder()
.setSubject(userDetails.getUsername()) // Podría ser "email" o "userId"
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS256, jwtSecret)
.compact();
}
// Validar JWT
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (ExpiredJwtException e) {
System.out.println("El token ha expirado.");
} catch (SignatureException e) {
System.out.println("Firma del token inválida.");
} catch (MalformedJwtException e) {
System.out.println("Token mal formado.");
}
return false;
}
// Obtener 'subject' (normalmente username o userId)
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser().setSigningKey(jwtSecret)
.parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
Filtro de autenticación con JWT
Para cada petición HTTP, necesitamos interceptar la cabecera Authorization y, si existe un token JWT, validarlo. Esto se hace con un filtro que extiende de OncePerRequestFilter:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromJWT(token);
// Cargar más detalles del usuario, por ejemplo roles.
// Podríamos tenerlos en la BD o codificados en los claims del token.
// Aquí asumimos un role de ejemplo:
UserDetails userDetails = new User(
username,
"",
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER”))
);
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
Configuración de seguridad
En SecurityConfig (extendiéndolo de WebSecurityConfigurerAdapter o usando las nuevas aproximaciones basadas en Beans), registramos el filtro y definimos qué endpoints estarán protegidos o abiertos:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/auth/login", "/auth/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Controlador de autenticación
Finalmente, necesitamos un controlador para manejar el login, donde:
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
// Autenticamos con el "AuthenticationManager" de Spring
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Generamos el JWT
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(Collections.singletonMap("token", jwt));
}
}
Pros:
Contras:
En muchos casos, se combinan varios patrones. Por ejemplo:
Esto ofrece lo mejor de ambos mundos:
En un e-commerce, podríamos:
Este enfoque reduce las barreras a nuevos usuarios (por ejemplo, no necesitan crear una cuenta más, sino que usan su cuenta de Google) y mantiene la eficiencia en la validación de tokens.
Tanto en Token-based como en JWT, podemos implementar la lógica de Refresh Tokens para evitar que el usuario se reloguee constantemente. La idea es la siguiente:
En JWT se puede usar un secreto compartido (HMAC) o par de claves asimétricas (RSA/ECDSA).
Nunca debemos almacenar la clave secreta en texto plano dentro del repositorio de código. Usar un gestor de secretos (Vault, AWS Secrets Manager, etc.) o variables de entorno. En entornos productivos, la seguridad de la clave es crítica para evitar falsificación de tokens.
Siempre es fundamental cifrar el canal de comunicación. Utilizar TLS (HTTPS) tanto para interacciones externas como para la comunicación interna entre microservicios (si es viable). Sin un canal cifrado, un atacante podría robar los tokens (sea JWT u otros).
En un e-commerce, es común requerir logs de auditoría para rastrear cambios sensibles (compras, cancelaciones, modificaciones de producto). Incorporar la información del usuario (ID, roles) en los logs facilita la trazabilidad y el cumplimiento de normas.
En muchos diseños de microservicios, se utiliza un API Gateway (por ejemplo, Spring Cloud Gateway o NGINX). El Gateway puede interceptar las peticiones y realizar la verificación de los tokens antes de enrutarlas a los microservicios internos.
Esto simplifica la lógica de seguridad, ya que no se necesita agregar los filtros de autenticación en cada microservicio, aunque estos podrían tener validaciones adicionales si lo requieren.
Característica | Token-based Auth | OAuth2 | JWT |
---|---|---|---|
Facilidad de implementación | Relativamente sencilla | Moderada-alta (varios flujos) | Moderada (requiere firma y parseo de tokens) |
Escenario típico | Apps internas, proyectos pequeños/medianos | Login con proveedores externos, delegación a un server | Microservicios con validación local, escalabilidad |
Necesidad de servicio central | Opcional (si se valida token en un store) | Sí (Authorization Server) | Solo para emisión, validación puede ser descentralizada |
Revocación | Sencilla si se almacena token en DB/Redis | A través del Authorization Server | Requiere listas negras o expiraciones cortas |
Tamaño del token | Normalmente pequeño (UUID) | Depende del proveedor (puede ser un token opaco) | Puede crecer con los claims |
Uso de claims | Opcional (puede guardarse solo un ID) | Sí, en algunos flujos (depende de ID Token) | Fuerte, claims definidos en el payload |
Siempre evalúa tu carga de trabajo, la arquitectura, la experiencia de usuario y la complejidad del sistema antes de decantarte por un patrón. En ocasiones, una mezcla de estos enfoques es la mejor solución.
En una aplicación compleja, donde se manejan transacciones y datos personales, es imprescindible diseñar con cuidado el flujo de acceso y la gestión de tokens.
La seguridad en una arquitectura de microservicios es un tema amplio, que abarca autenticación, autorización, integridad de datos y protección contra múltiples vectores de ataque.
Al final, la mejor elección depende de tu contexto de negocio, tu stack tecnológico y los requisitos de escalabilidad y seguridad. Muchos sistemas de producción combinan, por ejemplo, OAuth2 con emisión de JWT, y un gateway central que intercepta y verifica las solicitudes para simplificar la arquitectura del resto de microservicios.
En cualquier caso, lo fundamental es mantener siempre la seguridad como una prioridad desde las primeras fases de diseño, asegurando un enfoque sistemático y robusto que proteja la confidencialidad, integridad y disponibilidad de tus servicios y, sobre todo, de los datos de tus clientes.
Espero que te haya gustado esta sección de seguridad, la cual da para mucho que hablar. En la siguiente entrega veremos patrones de tolerancia a fallos que, como su propio nombre indica, nos permite que un sistema siga funcionando aun cuando ocurran errores o caídas de alguno de sus componentes.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.