Tokens Web JSON (JWT): Fundamentos y Aplicación Práctica

Los Tokens Web JSON (JWT) son un formato compacto para la representación de declaraciones (claims) que permite la transmisión segura de información entre partes. Están diseñados para entornos con limitaciones de espacio, siendo comúnmente utilizados en encabezados de autorización HTTP o parámetros de consulta de URI.

Un JWT encapsula estas declaraciones en un formato JSON. Este contenido JSON puede servir como la carga útil de una estructura JSON Web Signature (JWS) o como el texto plano (cifrado) de una estructura JSON Web Encryption (JWE). La integridad o la confidencialidad de estas declaraciones se asegura mediante códigos de autenticación de mensajes (MAC) y/o operaciones de cifrado.

  • JWE (JSON Web Encryption): Define una estructura para el contenido cifrado basado en JSON. Proporciona cifrado para secuencias de bytes arbitrarias, asegurando la integridad y dificultando la interceptación no autorizada.
  • JWS (JSON Web Signature): Describe un mecanismo para representar contenido autenticado con una firma digital o un MAC, utilizando estructuras JSON y codificación BASE64URL. Su objetivo es garantizar la integridad de los datos, es decir, que no han sido alterados.
  • JWA (JSON Web Algorithm): Especifica el conjunto de algoritmos de firma digital y MAC que pueden emplearse con JWS y JWE.

Mecanismos de Implementación de JWT

Los JWT se pueden implementar principalmente a través de dos especificaciones:

  • Basado en JWE: Esta implementación se apoya en algoritmos de cifrado y descifrado, codificación BASE64URL y mecanismos de autenticación para salvaguardar la confidencialidad y la integridad de las declaraciones transmitidas, haciendo que la información sea difícil de comprometer.
  • Basado en JWS: Utiliza codificación BASE64URL y firmas digitales para asegurar la integridad de las declaraciones. Esto significa que garantiza que los datos no han sido alterados durante el tránsito, pero las declaraciones permanecen legibles (en texto plano) para cualquiera que tenga acceso al token. La mayoría de los marcos de trabajo JWT actuales optan por la implementación JWS debido a su simplicidad y al menor coste computacional.

Las Declaraciones (Claims)

El propósito central de un JWT es proteger la integridad (y opcionalmente, la confidencialidad) de sus declaraciones. Estas declaraciones se presentan como un objeto JSON dentro del JWT, donde cada declaración es un par clave-valor.

Aunque no son obligatorias, existen declaraciones predefinidas en la especificación que son de uso común y se identifican con nombres cortos para mantener la compacidad del token:

Claim Nombre Completo Descripción
iss Issuer Entidad que emitió el JWT.
sub Subject Sujeto del JWT (entidad a la que se refiere el token).
aud Audience Partes para las cuales está destinado el JWT.
exp Expiration Time Tiempo de expiración del JWT, después del cual no debe ser aceptado.
nbf Not Before Tiempo antes del cual el JWT no debe ser aceptado.
iat Issued At Tiempo en que el JWT fue emitido.
jti JWT ID Identificador único para el JWT.

Los desarrolladores tienen la flexibilidad de definir sus propias declaraciones personalizadas, siempre y cuando sus nombres no entren en conflicto con las declaraciones estándar predefinidas.

El Encabezado (Header)

El encabezado de un JWT, conocido en la especificación como JOSE Header (Javascript Object Signing and Encryption), es un objeto JSON que contiene metadatos sobre el token. Generalmente, incluye el tipo de token (typ, que suele ser "JWT") y el algoritmo de firma o cifrado utilizado (alg).

Escenarios de Uso y Desafíos

En esencia, un JWT actúa como un token de seguridad, a menudo utilizado para mantener la "persistencia de la sesión" o para transmitir información de autenticación de forma segura (las declaraciones). A diferencia de las soluciones de ID de sesión tradicionales (como el JSESSIONID de Tomcat), donde el servidor genera y persiste la sesión y el cliente la almacena en una cookie, los JWT son inherentemente sin estado.

Esto significa que el servidor no necesita almacenar información sobre la sesión del cliente después de emitir el token. Toda la información relevante se codifica dentro del propio JWT. Esta naturaleza sin estado simplifica la escalabilidad horizontal, ya que cualquier servidor puede validar un JWT sin necesidad de acceder a un almacenamiento de sesión centralizado.

Consideraciones de Seguridad Cruciales para JWT

La naturaleza sin estado de los JWT implica que, una vez emitidos, cualquier cliente que posea un token válido puede interactuar con el servicio. Si un JWT se filtra, puede generar graves problemas de seguridad. Por ello, se recomienda implementar las siguientes prácticas:

  • Definir y validar el tiempo de expiración (exp): Es fundamental establecer un tiempo de vida corto para los tokens y asegurarse de que se valide estrictamente.
  • Implementar listas negras (blacklist) o listas de revocación: Para tokens que necesitan ser invalidados antes de su expiración (por ejemplo, al cerrar sesión o por detección de compromiso), se puede usar la declaración jti (JWT ID) para rastrear y bloquear tokens específicos. Esto se puede lograr con estructuras de datos eficientes como filtros de Bloom o bases de datos como Redis.
  • Utilizar algoritmos de firma robustos: Emplear algoritmos de firma digital fuertes, como RS256 o RS512, para prevenir la manipulación del token.
  • Evitar información sensible en las declaraciones: Dado que las declaraciones en un JWS son legibles, no se debe incluir información altamente confidencial directamente en el token. Si la confidencialidad es clave, se debe usar JWE.
  • Autenticación secundaria para operaciones de alto riesgo: Para transacciones críticas (como pagos), la autenticación solo con JWT no es suficiente. Se requieren métodos de verificación adicionales, como OTP por SMS, autenticación biométrica, etc.

Uso con Pasarelas API

Los JWT son ideales para escenarios de autenticación, especialmente cuando se integran con pasarelas API. Una pasarela API puede gestionar rutas que no requieren autenticación y otras que sí. Para estas últimas, la pasarela puede validar el JWT, extraer sus declaraciones y usarlas para la autorización o para pasar información al servicio backend.

  • Filtros de autenticación específicos: En frameworks como Spring Cloud Gateway, se puede implementar un WebFilter o GlobalFilter personalizado para interceptar solicitudes, validar el JWT y, si es necesario, inyectar los datos del token en el contexto de la solicitud.
  • Listas blancas de URI: Para rutas que no requieren autenticación, se puede mantener una lista blanca de URI que la pasarela ignorará en el proceso de verificación de JWT.

Es vital recordar que las implementaciones JWS, las más comunes, no cifran la carga útil. Solo garantizan la integridad. Por tanto, son sin estado pero no confidenciales. Las operaciones críticas siempre deben complementarse con mecanismos de autenticación adicionales y una gestión robusta de listas negras.

Implementación de JWT con jjwt

A continuación, se presenta un ejemplo de cómo implementar la generación y validación de JWT utilizando la biblioteca jjwt en Java.

Dependencias Maven


<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>
<!-- Apache Commons Lang3 para utilidades de fecha -->
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.12.0</version>
</dependency>
<!-- Lombok para DTOs (opcional) -->
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.24</version>
   <scope>provided</scope>
</dependency>
   

Clase de Utilidad para Gestión de Tokens


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
@Slf4j
public class GestorTokensJWT {

   private final SignatureAlgorithm ALGORITMO_FIRMA = SignatureAlgorithm.RS512;
   private PrivateKey clavePrivada;
   private PublicKey clavePublica;

   // TODO: Reemplazar con tus claves RSA reales codificadas en Base64
   private static final String CLAVE_PUBLICA_RSA_BASE64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAslJkG4zQ1V7X6b..."; // Tu clave pública real
   private static final String CLAVE_PRIVADA_RSA_BASE64 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC..."; // Tu clave privada real

   public GestorTokensJWT() {
       try {
           this.clavePublica = UtilidadesClaveRSA.obtenerClavePublica(CLAVE_PUBLICA_RSA_BASE64);
           this.clavePrivada = UtilidadesClaveRSA.obtenerClavePrivada(CLAVE_PRIVADA_RSA_BASE64);
       } catch (Exception e) {
           log.error("Error al inicializar claves RSA para JWT", e);
           throw new IllegalStateException("Fallo crítico al cargar las claves RSA para JWT.", e);
       }
   }

   /**
    * Genera un nuevo token JWT firmado.
    * @param datosSolicitud Objeto con las declaraciones estándar del token.
    * @param tiempoVidaMillis Duración de validez del token en milisegundos.
    * @param atributosAdicionales Un mapa de declaraciones personalizadas.
    * @return RespuestaGeneracionToken que contiene el token y su fecha de expiración.
    */
   public RespuestaGeneracionToken generarTokenJWT(SolicitudDatosJWT datosSolicitud, long tiempoVidaMillis, Map<String, Object> atributosAdicionales) {
       if (this.clavePrivada == null) {
           log.error("Clave privada no inicializada. No se puede generar el token JWT.");
           throw new IllegalStateException("Clave privada RSA no disponible para firmar el token.");
       }

       RespuestaGeneracionToken respuesta = new RespuestaGeneracionToken();
       Map<String, Object> claims = new HashMap<>();
       long ahoraMillis = System.currentTimeMillis();
       Date ahora = new Date(ahoraMillis);

       // Establecer declaraciones estándar
       claims.put(Claims.ISSUER, datosSolicitud.getEmisor());
       claims.put(Claims.SUBJECT, datosSolicitud.getAsunto());
       claims.put(Claims.AUDIENCE, datosSolicitud.getAudiencia());
       claims.put(Claims.ID, datosSolicitud.getIdToken() != null ? datosSolicitud.getIdToken() : UUID.randomUUID().toString()); // JTI

       // Añadir atributos personalizados si existen
       if (atributosAdicionales != null) {
           claims.putAll(atributosAdicionales);
       }

       JwtBuilder constructorJWT = Jwts.builder()
                                       .setClaims(claims)
                                       .setIssuedAt(ahora); // iat

       // Configurar expiración y 'not before'
       if (tiempoVidaMillis > 0) {
           long expiracionMillis = ahoraMillis + tiempoVidaMillis;
           Date fechaExpiracion = new Date(expiracionMillis);
           constructorJWT.setExpiration(fechaExpiracion)
                         .setNotBefore(ahora);
           respuesta.setFechaExpiracion(DateFormatUtils.format(fechaExpiracion, "yyyy-MM-dd HH:mm:ss"));
       } else {
            log.warn("Generando token JWT sin tiempo de vida (exp) explícito.");
       }

       // Firmar el token con la clave privada RS512
       constructorJWT.signWith(ALGORITMO_FIRMA, clavePrivada);

       String tokenGenerado = constructorJWT.compact();
       respuesta.setTokenAcceso(tokenGenerado);
       respuesta.setTiempoRenovacion(tiempoVidaMillis);
       return respuesta;
   }

   /**
    * Valida un token JWT y extrae sus declaraciones.
    * @param tokenJWT El token JWT a validar.
    * @return Las declaraciones (Claims) si el token es válido, null en caso contrario.
    */
   public Claims validarYObtenerClaims(String tokenJWT) {
       if (this.clavePublica == null) {
           log.error("Clave pública no inicializada. No se puede validar el token JWT.");
           throw new IllegalStateException("Clave pública RSA no disponible para verificar el token.");
       }

       Claims claims = null;
       try {
           // Se usa la clave pública para verificar la firma de un token RS512
           claims = Jwts.parserBuilder()
                        .setSigningKey(clavePublica) // Importante: usar clave pública para verificación RSA
                        .setAllowedClockSkewSeconds(60) // Tolerancia de 60 segundos para diferencias de reloj
                        .build()
                        .parseClaimsJws(tokenJWT)
                        .getBody();
       } catch (ExpiredJwtException ex) {
           log.warn("Token JWT expirado: {}", ex.getMessage());
           // Se podría lanzar una excepción personalizada aquí si se desea
       } catch (Exception ex) {
           log.error("Token JWT inválido o con firma no válida: {}", ex.getMessage());
           // Se podría lanzar una excepción personalizada aquí
       }
       return claims;
   }
}

Clases de DTO y Utilidades


// DTO para la solicitud de creación de JWT
@Data
@NoArgsConstructor
@AllArgsConstructor
class SolicitudDatosJWT {
   private String emisor; // iss
   private String asunto; // sub
   private String audiencia; // aud
   private String idToken; // jti (opcional, si no se provee se genera uno)
}

// DTO para la respuesta de creación de JWT
@Data
@NoArgsConstructor
@AllArgsConstructor
class RespuestaGeneracionToken {
   private String tokenAcceso;
   private String fechaExpiracion;
   private long tiempoRenovacion; // Tiempo de vida en milisegundos
}

// Clase de utilidad para cargar claves RSA desde strings Base64
class UtilidadesClaveRSA {
   public static PublicKey obtenerClavePublica(String claveBase64) throws Exception {
       byte[] bytesClave = Base64.getDecoder().decode(claveBase64);
       X509EncodedKeySpec especificaciones = new X509EncodedKeySpec(bytesClave);
       KeyFactory fabricaClaves = KeyFactory.getInstance("RSA");
       return fabricaClaves.generatePublic(especificaciones);
   }

   public static PrivateKey obtenerClavePrivada(String claveBase64) throws Exception {
       byte[] bytesClave = Base64.getDecoder().decode(claveBase64);
       PKCS8EncodedKeySpec especificaciones = new PKCS8EncodedKeySpec(bytesClave);
       KeyFactory fabricaClaves = KeyFactory.getInstance("RSA");
       return fabricaClaves.generatePrivate(especificaciones);
   }
}

Ejemplo de Uso / Prueba


import io.jsonwebtoken.Claims;
import java.util.HashMap;
import java.util.Map;

public class PruebaGestionJWT {
   public static void main(String[] args) {
       // En un entorno Spring, GestorTokensJWT sería inyectado. Aquí lo instanciamos directamente.
       GestorTokensJWT gestorTokens = new GestorTokensJWT();

       // 1. Preparar datos para el token
       SolicitudDatosJWT datos = new SolicitudDatosJWT(
           "MiServicioDeAutenticacion", // emisor (iss)
           "usuario_ejemplo@dominio.com", // asunto (sub)
           "AplicacionClienteWeb", // audiencia (aud)
           "ID_JWT_" + System.currentTimeMillis() // idToken (jti)
       );

       Map<String, Object> atributosPersonalizados = new HashMap<>();
       atributosPersonalizados.put("idUsuarioDB", "USR-7890");
       atributosPersonalizados.put("nombreCompleto", "Usuario de Prueba");
       atributosPersonalizados.put("permisos", new String[]{"admin", "editor"});

       long tiempoValidez = 3600 * 1000; // 1 hora en milisegundos

       // 2. Generar el token
       RespuestaGeneracionToken respuestaGeneracion = gestorTokens.generarTokenJWT(
           datos,
           tiempoValidez,
           atributosPersonalizados
       );

       System.out.println("--- Token Generado ---");
       System.out.println("Token de Acceso: " + respuestaGeneracion.getTokenAcceso());
       System.out.println("Fecha de Expiración: " + respuestaGeneracion.getFechaExpiracion());
       System.out.println("Tiempo de Renovación (ms): " + respuestaGeneracion.getTiempoRenovacion());

       // 3. Validar y parsear el token generado
       Claims claimsExtraidos = gestorTokens.validarYObtenerClaims(respuestaGeneracion.getTokenAcceso());

       if (claimsExtraidos != null) {
           System.out.println("\n--- Claims Extraídos del Token ---");
           System.out.println("Emisor (iss): " + claimsExtraidos.getIssuer());
           System.out.println("Sujeto (sub): " + claimsExtraidos.getSubject());
           System.out.println("Audiencia (aud): " + claimsExtraidos.getAudience());
           System.out.println("ID JWT (jti): " + claimsExtraidos.getId());
           System.out.println("Emitido el (iat): " + claimsExtraidos.getIssuedAt());
           System.out.println("Expira el (exp): " + claimsExtraidos.getExpiration());

           // Acceder a claims personalizados
           System.out.println("ID de Usuario de DB: " + claimsExtraidos.get("idUsuarioDB"));
           System.out.println("Nombre Completo: " + claimsExtraidos.get("nombreCompleto"));
           System.out.println("Permisos: " + claimsExtraidos.get("permisos").toString());
       } else {
           System.out.println("\nEl token no pudo ser validado o está expirado.");
       }

       // Simular un token alterado (esto fallaría la validación)
       // String tokenAlterado = respuestaGeneracion.getTokenAcceso() + "MODIFICADO";
       // Claims claimsAlterados = gestorTokens.validarYObtenerClaims(tokenAlterado);
       // if (claimsAlterados == null) {
       //     System.out.println("\n(Prueba) Token alterado: La validación falló como se esperaba.");
       // }
   }
}

Desafíos de Rendimiento con JWT (RS256)

En sistemas de alta concurrencia, la generación de JWTs firmados con algoritmos asimétricos como RS256 o RS512 (que involucran RSA) puede convertirse en una operación intensiva en CPU. Un problema común surge cuando un gran número de JWTs expiran y necesitan ser regenerados simultáneamente, por ejemplo, en escenarios donde los usuarios son inactivos durante un largo período y luego son reactivados por una campaña de marketing.

Esto puede llevar a un pico de uso de CPU en la pasarela API o el servicio de autenticación, causando latencia y fallos temporales (fenómeno de "muerte aparente" o "estancamiento" del servicio, con timeouts en Nginx y alta carga de CPU).

Para mitigar este problema, se pueden considerar las siguientes estrategias:

  • Aleatorizar los tiempos de expiración: En lugar de fijar un tiempo de vida exacto (e.g., 7 días), se puede añadir un componente aleatorio (e.g., entre 1 y 24 horas) al tiempo base de expiración. Esto ayuda a distribuir la carga de regeneración de tokens a lo largo del tiempo, en lugar de concentrarla en un único momento.
  • Escalar horizontalmente la infraestructura: Asegurar que los servicios de autenticación y las pasarelas API puedan escalar automáticamente (escalamiento elástico) o manualmente para manejar picos de tráfico. Las soluciones de nube ofrecen opciones robustas para este tipo de escenarios.
  • Optimizar la gestión de claves: Aunque el proceso de firma RSA es inherentemente costoso, asegurarse de que las claves se carguen y gestionen de manera eficiente puede ayudar a minimizar la sobrecarga.

Renovación Automática de Tokens

Dado que el tiempo de expiración (exp) de un JWT es inmutable una vez que el token ha sido firmado, el JWT no puede "refrescarse" en sí mismo. Una vez que un token caduca, se vuelve inválido. Para mantener una sesión de usuario de larga duración sin emitir tokens de vida excesivamente prolongada (lo que aumenta el riesgo de seguridad), se emplean mecanismos de renovación.

  • Almacenamiento de estado en Redis (Desaconsejado): Una propuesta es emitir JWTs sin tiempo de expiración y gestionar la validez en una base de datos externa como Redis. Cada operación con el token refrescaría su validez en Redis. Sin embargo, esta solución contradice la naturaleza sin estado de los JWTs, convirtiéndolos en tokens con estado y eliminando gran parte de sus beneficios arquitectónicos. Si se va a persistir información en Redis, podría ser más directo almacenar toda la sesión del usuario allí.
  • Uso de Tokens de Refresco (Solución Preferida): La estrategia más aceptada es usar un "token de refresco" (refresh token) junto con el JWT de acceso (access token). El JWT de acceso tiene una vida útil corta. Cuando está a punto de expirar, el cliente puede usar el token de refresco (que tiene una vida útil más larga y suele ser un token de estado, almacenado en el servidor o en una base de datos segura) para solicitar un nuevo JWT de acceso y, opcionalmente, un nuevo token de refresco. Esto permite sesiones prolongadas sin comprometer la seguridad de los tokens de acceso de corta duración.

Terminación Activa (Reovcación)

A pesar de la naturaleza sin estado de los JWTs, a menudo es necesario tener la capacidad de invalidar un token antes de su tiempo de expiración natural (por ejemplo, cuando un usuario cierra sesión o en caso de compromiso de seguridad). Esto requiere introducir un componente con estado en el sistema.

  • Listas Negras de Tokens (Blacklisting): La estrategia más común es mantener una "lista negra" (blacklist) de tokens revocados. Cuando un usuario cierra sesión, su JWT actual se añade a esta lista negra. Cada vez que se recibe un JWT, el sistema primero verifica si está presente en la lista negra antes de proceder con la validación de la firma y la expiración.
  • Implementación: La lista negra puede implementarse utilizando una base de datos rápida (como Redis) o una tabla en una base de datos persistente. La declaración jti (JWT ID) es ideal para identificar un token específico en la lista negra.

Ejemplo de Implementación de Blacklisting

Al cerrar sesión, se registra el token actual:


import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

// Suponiendo un servicio de lista negra inyectado: tokenBlacklistService
// Y una entidad SysTokenBlackList para almacenar tokens revocados

public class ControladorAutenticacion {

   private final ServicioListaNegraTokens tokenBlacklistService; // Inyectar este servicio

   public ControladorAutenticacion(ServicioListaNegraTokens tokenBlacklistService) {
       this.tokenBlacklistService = tokenBlacklistService;
   }

   public RespuestaSimple cerrarSesion(HttpServletRequest request) {
       String tokenAAcceso = request.getHeader("Authorization"); // O el nombre de tu encabezado de token
       if (tokenAAcceso != null && tokenAAcceso.startsWith("Bearer ")) {
           String token = tokenAAcceso.substring(7); // Eliminar "Bearer "

           EntidadTokenRevocado tokenRevocado = new EntidadTokenRevocado();
           tokenRevocado.setToken(token);
           tokenRevocado.setFechaRevocacion(LocalDateTime.now());
           // Opcional: Extraer la fecha de expiración del token para limpiar la lista negra
           // Claims claims = gestorTokens.validarYObtenerClaims(token);
           // if (claims != null) tokenRevocado.setFechaExpiracion(claims.getExpiration());

           tokenBlacklistService.guardarTokenRevocado(tokenRevocado);
           return new RespuestaSimple("Cierre de sesión exitoso.", true);
       }
       return new RespuestaSimple("No se encontró token para cerrar sesión.", false);
   }
}

Durante la validación del token (por ejemplo, en un filtro o interceptor), se verifica si el token está en la lista negra:


import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

// Suponiendo un servicio de lista negra inyectado y un gestor de tokens
// GestorTokensJWT gestorTokens;
// ServicioListaNegraTokens tokenBlacklistService;

public class FiltroAutenticacionJWT implements Filter {

   private final GestorTokensJWT gestorTokens;
   private final ServicioListaNegraTokens tokenBlacklistService;

   public FiltroAutenticacionJWT(GestorTokensJWT gestorTokens, ServicioListaNegraTokens tokenBlacklistService) {
       this.gestorTokens = gestorTokens;
       this.tokenBlacklistService = tokenBlacklistService;
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {
       HttpServletRequest httpRequest = (HttpServletRequest) request;
       String authHeader = httpRequest.getHeader("Authorization");

       if (authHeader != null && authHeader.startsWith("Bearer ")) {
           String token = authHeader.substring(7);

           // 1. Verificar si el token está en la lista negra
           if (tokenBlacklistService.estaTokenRevocado(token)) {
               // Devolver un error de token inválido/revocado
               // ...
               return;
           }

           // 2. Si no está en la lista negra, validar el token
           Claims claims = gestorTokens.validarYObtenerClaims(token);
           if (claims != null) {
               // Token válido y no revocado.
               // Proceder con la autorización o inyectar claims en la solicitud.
               chain.doFilter(request, response);
               return;
           }
       }
       // Token ausente, inválido o no autorizado.
       // ...
   }
   // ... otros métodos de Filter
}

Etiquetas: JWT JWS JWE jjwt autenticación

Publicado el 9-12 04:39