Mensagem de erro
JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
Também aparece 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
O que significa
O JJWT recalculou (HMAC) ou verificou (RSA, ECDSA, EdDSA) a assinatura sobre o cabeçalho e o payload do token com a chave que você configurou, e o resultado não correspondeu ao segmento de assinatura do token. O token foi analisado sem problemas e o algoritmo combinava com a chave; a chave e o token simplesmente não pertencem um ao outro, ou o token mudou depois de ser assinado.
Por que acontece
O verificador usa uma chave diferente
comumA chave de outro ambiente, um par de chaves gerado de novo a cada inicialização (um bug comum em demos promovidas a produção) ou uma instância do serviço com um segredo desatualizado.
Mesmo segredo HMAC, bytes diferentes
comumUm lado usa os bytes UTF-8 da string, o outro a decodifica de base64 primeiro (os exemplos do Spring costumam guardar segredos em base64). Os segredos parecem idênticos na configuração e produzem MACs diferentes.
Rotação de chaves sem tratamento do kid
ocasionalO emissor assina com uma chave nova enquanto o verificador ainda só tem a antiga. Sem um localizador de chaves indexado pelo `kid`, o verificador não consegue nem saber qual chave era a pretendida.
O token foi alterado
ocasionalQualquer alteração no segmento do cabeçalho ou do payload — inclusive espaços em branco ou uma nova codificação — invalida a assinatura.
Como corrigir
- 1.
Resolver chaves pelo kid com um localizador de chaves
O JJWT 0.12+ oferece suporte a `keyLocator`, que recebe o cabeçalho e retorna a chave certa. Combine-o com um JWKS que você atualiza a cada rotação.
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.
Usar os mesmos bytes de chave HMAC em todos os lados
Decida se o segredo configurado é texto ou base64 e derive a chave da mesma forma em todos os lados. A calculadora de HMAC mostra o MAC nas duas interpretações.
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")));