Mensaje de error
JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
También se muestra como
- io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
JJWT (Java) — io.jsonwebtoken.security.SignatureException from JwtParser.parseSignedClaims
Qué significa
JJWT volvió a calcular (HMAC) o verificó (RSA, ECDSA, EdDSA) la firma sobre el encabezado y la carga útil del token con la clave configurada, y no coincidió con el segmento de firma del token. El token se analizó bien y el algoritmo encajaba con la clave; simplemente la clave y el token no van juntos, o el token cambió después de firmarse.
Por qué ocurre
El verificador usa otra clave
frecuenteLa clave de otro entorno, un par de claves que se regenera al arrancar (un fallo habitual en demos que acaban en producción) o una instancia del servicio con un secreto desactualizado.
Mismo secreto HMAC, bytes distintos
frecuenteUn lado usa los bytes UTF-8 de la cadena y el otro la decodifica primero desde base64 (los ejemplos de Spring suelen guardar los secretos en base64). Los secretos parecen idénticos en la configuración y producen MAC distintos.
Rotación de claves sin gestión del kid
ocasionalEl emisor firma con una clave nueva mientras el verificador sigue teniendo solo la antigua. Sin un localizador de claves que busque por `kid`, el verificador ni siquiera puede saber qué clave corresponde.
El token se modificó
ocasionalCualquier cambio en el segmento del encabezado o de la carga útil —incluidos espacios en blanco o una nueva codificación— invalida la firma.
Cómo solucionarlo
- 1.
Resolver las claves por kid con un localizador de claves
JJWT 0.12+ admite `keyLocator`, que recibe el encabezado y devuelve la clave correcta. Combínalo con un JWKS que actualices en cada rotación.
java import io.jsonwebtoken.*; import java.security.Key; // Pick the verification key by the token's kid instead of a single static key. Locator<Key> keys = new LocatorAdapter<>() { @Override protected Key locate(JwsHeader header) { Key key = publicKeysByKid.get(header.getKeyId()); // refreshed from the issuer's JWKS if (key == null) throw new UnsupportedJwtException("unknown kid " + header.getKeyId()); return key; } }; Jws<Claims> jws = Jwts.parser().keyLocator(keys).requireIssuer("https://issuer.example.com/").build().parseSignedClaims(token); - 2.
Acordar los bytes de la clave HMAC
Decide si el secreto configurado es texto o base64 y deriva la clave de la misma forma en todos los lados. La calculadora HMAC muestra el MAC con ambas interpretaciones.
java // Both sides must derive the same key bytes. If the secret is stored base64-encoded, decode it. javax.crypto.SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor( java.util.Base64.getDecoder().decode(System.getenv("JWT_SECRET_B64")));