- Descripción General
- Arquitectura del Sistema
- Configuración Inicial
- Componentes Principales
- Flujo de Autenticación
- Configuración de Seguridad
- Uso de la API
- Consideraciones de Seguridad
- Mejoras para Producción
- Troubleshooting
Este proyecto implementa autenticación JWT (JSON Web Tokens) en Spring Boot con las siguientes características:
- ✅ Autenticación basada en JWT
- ✅ Autorización por roles
- ✅ Revocación de tokens
- ✅ Configuración stateless
- ✅ Validación de tokens
- ✅ Gestión de usuarios
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Spring Boot │ │ PostgreSQL │
│ (Cliente) │◄──►│ (Backend) │◄──►│ (Base de │
│ │ │ │ │ Datos) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ JWT Token │
│ (Memoria/ │
│ Redis) │
└─────────────────┘
- AuthController: Maneja endpoints de autenticación
- JwtService: Genera y valida tokens JWT
- JwtAuthenticationFilter: Filtro que valida tokens en cada request
- TokenRevocationService: Maneja la revocación de tokens
- SecurityConfig: Configuración de Spring Security
- User: Entidad que implementa UserDetails
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Validación -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency># application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
spring.datasource.username=postgres
spring.datasource.password=mysecretpassword
spring.jpa.hibernate.ddl-auto=update# JWT Configuration
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
jwt.expiration=86400000@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody AuthRequest request) {
// 1. Autenticar credenciales
// 2. Generar JWT token
// 3. Retornar token y datos del usuario
}
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestBody LogoutRequest request) {
// Revocar token JWT
}
}@Service
public class JwtService {
public String generateToken(UserDetails userDetails) {
// Generar token JWT con claims
}
public boolean isTokenValid(String token, UserDetails userDetails) {
// Validar token JWT
}
public String extractUsername(String token) {
// Extraer username del token
}
}@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
// 1. Extraer token del header Authorization
// 2. Validar token
// 3. Establecer autenticación en contexto
}
}sequenceDiagram
participant C as Cliente
participant A as AuthController
participant S as JwtService
participant U as UserService
participant DB as Base de Datos
C->>A: POST /api/auth/login
A->>A: Validar credenciales
A->>S: Generar JWT token
S->>A: Token JWT
A->>U: Obtener datos del usuario
U->>DB: Consultar usuario
DB->>U: Datos del usuario
U->>A: UserDto
A->>C: Token + Datos del usuario
sequenceDiagram
participant C as Cliente
participant F as JwtFilter
participant S as JwtService
participant U as UserService
participant SC as SecurityContext
C->>F: Request con Bearer token
F->>F: Extraer token del header
F->>S: Validar token
S->>F: Token válido
F->>U: Cargar UserDetails
U->>F: UserDetails
F->>SC: Establecer autenticación
F->>C: Request procesado
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
http
.csrf(csrf -> csrf.disable()) // Deshabilitar CSRF para JWT
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll() // Endpoints públicos
.anyRequest().authenticated() // Resto requiere autenticación
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Sin sesiones
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}curl -X POST http://localhost:8190/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@example.com",
"password": "password123"
}'Respuesta:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "Admin User",
"email": "admin@example.com",
"role": "ADMIN"
}
}curl -X GET http://localhost:8190/api/auth/test \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."curl -X POST http://localhost:8190/api/auth/logout \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d '{
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'- ✅ Usar una clave secreta fuerte (mínimo 256 bits)
- ✅ Almacenar en variables de entorno
- ✅ Rotar periódicamente en producción
# Generar clave secreta segura
jwt.secret=${JWT_SECRET:404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970}- ✅ Configurar tiempo de expiración apropiado
- ✅ Implementar refresh tokens para aplicaciones web
- ✅ Considerar diferentes tiempos para diferentes tipos de tokens
jwt.expiration=86400000 # 24 horas
jwt.refresh-expiration=604800000 # 7 días- ✅ Mantener lista de tokens revocados
- ✅ Limpiar tokens expirados automáticamente
- ✅ Usar Redis en producción para mejor rendimiento
// Agregar headers de seguridad
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");@Service
public class RedisTokenRevocationService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void revokeToken(String token) {
String key = "revoked:" + token;
redisTemplate.opsForValue().set(key, "revoked",
Duration.ofHours(24));
}
}public class RefreshTokenService {
public String generateRefreshToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() +
refreshTokenExpiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
}@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Integer> requestCount = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(...) {
String clientIp = request.getRemoteAddr();
int count = requestCount.getOrDefault(clientIp, 0);
if (count > 100) { // 100 requests por minuto
response.setStatus(429); // Too Many Requests
return;
}
requestCount.put(clientIp, count + 1);
filterChain.doFilter(request, response);
}
}@Component
public class SecurityAuditFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(SecurityAuditFilter.class);
@Override
protected void doFilterInternal(...) {
String token = extractToken(request);
String user = extractUserFromToken(token);
logger.info("Request from user: {} to endpoint: {}",
user, request.getRequestURI());
filterChain.doFilter(request, response);
}
}Solución:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>Verificar:
- Clave secreta correcta
- Algoritmo de firma (HS256)
- Tiempo de expiración
- Formato del token (Bearer + token)
Solución:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}Optimizaciones:
- Usar Redis para tokens revocados
- Implementar cache para UserDetails
- Usar async processing para validaciones
- Optimizar consultas de base de datos
- Spring Security Documentation
- JWT.io - Debugger de tokens JWT
- JJWT Library - Biblioteca JWT para Java
- OWASP JWT Cheat Sheet