Skip to content

luismolina-dev/JWT

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Guía de Implementación JWT en Spring Boot

📋 Tabla de Contenidos

  1. Descripción General
  2. Arquitectura del Sistema
  3. Configuración Inicial
  4. Componentes Principales
  5. Flujo de Autenticación
  6. Configuración de Seguridad
  7. Uso de la API
  8. Consideraciones de Seguridad
  9. Mejoras para Producción
  10. Troubleshooting

🎯 Descripción General

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

🏗️ Arquitectura del Sistema

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Frontend      │    │   Spring Boot   │    │   PostgreSQL    │
│   (Cliente)     │◄──►│   (Backend)     │◄──►│   (Base de      │
│                 │    │                 │    │    Datos)       │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                       ┌─────────────────┐
                       │   JWT Token     │
                       │   (Memoria/     │
                       │    Redis)       │
                       └─────────────────┘

Componentes Principales:

  1. AuthController: Maneja endpoints de autenticación
  2. JwtService: Genera y valida tokens JWT
  3. JwtAuthenticationFilter: Filtro que valida tokens en cada request
  4. TokenRevocationService: Maneja la revocación de tokens
  5. SecurityConfig: Configuración de Spring Security
  6. User: Entidad que implementa UserDetails

⚙️ Configuración Inicial

1. Dependencias Maven

<!-- 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>

2. Configuración de Base de Datos

# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
spring.datasource.username=postgres
spring.datasource.password=mysecretpassword
spring.jpa.hibernate.ddl-auto=update

3. Configuración JWT

# JWT Configuration
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
jwt.expiration=86400000

🔧 Componentes Principales

1. AuthController

@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
    }
}

2. JwtService

@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
    }
}

3. JwtAuthenticationFilter

@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
    }
}

🔄 Flujo de Autenticación

1. Login

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
Loading

2. Request Autenticado

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
Loading

🛡️ Configuración de Seguridad

SecurityConfig

@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();
    }
}

📡 Uso de la API

1. Login

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"
  }
}

2. Request Autenticado

curl -X GET http://localhost:8190/api/auth/test \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

3. Logout

curl -X POST http://localhost:8190/api/auth/logout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{
    "token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

🔒 Consideraciones de Seguridad

1. Clave Secreta

  • ✅ 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}

2. Expiración de Tokens

  • ✅ 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

3. Revocación de Tokens

  • ✅ Mantener lista de tokens revocados
  • ✅ Limpiar tokens expirados automáticamente
  • ✅ Usar Redis en producción para mejor rendimiento

4. Headers de Seguridad

// 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");

🚀 Mejoras para Producción

1. Redis para Revocación

@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));
    }
}

2. Refresh Tokens

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();
    }
}

3. Rate Limiting

@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);
    }
}

4. Logging de Seguridad

@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);
    }
}

🔧 Troubleshooting

Problema: "Cannot resolve symbol 'Valid'"

Solución:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Problema: Token inválido

Verificar:

  1. Clave secreta correcta
  2. Algoritmo de firma (HS256)
  3. Tiempo de expiración
  4. Formato del token (Bearer + token)

Problema: CORS

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;
}

Problema: Performance

Optimizaciones:

  1. Usar Redis para tokens revocados
  2. Implementar cache para UserDetails
  3. Usar async processing para validaciones
  4. Optimizar consultas de base de datos

📚 Recursos Adicionales

About

Authentication system with JWT

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages