๐ด The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
at io.jsonwebtoken.impl.DefaultJwtParser.verifySignature(DefaultJwtParser.java:456)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:321)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:263)โก Quick Fix Works 80% of the time
Ensure the secret key used in Jwts.builder().signWith() is exactly the same as the key in Jwts.parser().verifyWith().
String secret = "my-super-secret-key"; // Must be identical on both sides
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
// Verify
Jwts.parser().verifyWith(key).build().parseSignedClaims(token);๐ง Why this Happens
Tap to expand the deep technical explanation
When the backend (or another microservice) tried to verify the JWT, it recalculated the signature using its configured secret key. The calculated signature did not match the signature attached to the token. This means either the token was tampered with, or the verifying server is using a different secret key than the server that generated the token.
The HITEC City Parking Spot Analogy:
It's like a wax seal on a medieval letter. The king presses his ring into the wax to seal it. When the letter arrives, the recipient checks the wax seal against the king's ring. If the seal doesn't match, the letter is fake.
๐ How to Reproduce Confirm this is your error
Generate a JWT using secret key 'KEY_A'. Attempt to parse and verify that same JWT using a Spring Boot app configured with secret key 'KEY_B'.
๐ ๏ธ Solutions (5 Ways to Fix)
Match the secret keys across services
๐ Use this in a microservices architecture where multiple services verify the same token.
Centralize the JWT secret in an environment variable or config server, and ensure all services read from it.
# application.properties (Must be identical everywhere)
app.jwt.secret=my-super-secret-key-must-be-at-least-32-bytes-longCheck for key rotation logic
๐ Use this if you recently changed your secret key.
Old tokens signed with the previous key will fail verification. You must either invalidate all sessions or support multiple keys during the transition.
// Implement a custom JwtParser that tries multiple keys
// Key oldKey = ...
// Key newKey = ...
// try { parse with newKey } catch { parse with oldKey }Ensure token isn't truncated by HTTP headers
๐ Use this if the keys match but the error persists.
Web servers (Tomcat/Nginx) have limits on header sizes. If the JWT is huge (contains many claims), it might get truncated, breaking the signature.
# application.properties (Increase Tomcat header limit)
server.max-http-request-header-size=16KBVerify Algorithm matches (HS256 vs RS256)
๐ Use this if you are mixing symmetric and asymmetric cryptography.
If the token was signed with an RSA private key (RS256), you cannot verify it with an HMAC secret key. You must use the RSA public key.
// For RS256
// PublicKey publicKey = ...
// Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(token);Handle the exception in JwtAuthenticationFilter
๐ Use this to prevent 500 errors when a user sends a bad token.
Catch SignatureException in your filter and return a 401 Unauthorized response cleanly.
try {
String user = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload().getSubject();
} catch (SignatureException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid JWT Signature");
}๐ Version Notes
Often uses jjwt 0.11.x (`parseClaimsJws()`).
Uses jjwt 0.12.x (`verifyWith(key).build().parseSignedClaims()`). API is slightly different.
๐ก๏ธ How to Prevent This Next Time
Use a centralized Authentication Service to issue tokens, and share the public key (for RS256) or secret (for HS256) via a secure vault (like AWS Secrets Manager) to all resource servers.