JJWT (Java)Java
错误信息
JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
也可能输出为
- 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
含义
JJWT 用你配置的密钥,对令牌的头部和载荷重新计算(HMAC)或验证(RSA、ECDSA、EdDSA)签名,结果与令牌的签名段不符。令牌解析正常,算法也与密钥相符;只是密钥和令牌并不配套,或者令牌在签名后被改动过。
原因
验证方使用的是另一个密钥
常见另一个环境的密钥、每次启动时重新生成的密钥对(演示代码被直接用于生产时的常见 bug),或者某个服务实例使用的密钥已经过时。
HMAC 密钥相同,字节却不同
常见一方使用字符串的 UTF-8 字节,另一方先对它做 base64 解码(Spring 的示例常常以 base64 形式存储密钥)。这两个密钥在配置中看起来完全相同,算出的 MAC 却不同。
密钥轮换时没有处理 kid
偶见颁发者已经用新密钥签名,而验证方手里仍然只有旧密钥。如果没有按 `kid` 查找密钥的密钥定位器,验证方甚至无法判断令牌指的是哪个密钥。
令牌被改动过
偶见对头部段或载荷段的任何改动——包括空白字符的变化或重新编码——都会让签名失效。
修复方法
- 1.
用密钥定位器按 kid 查找密钥
JJWT 0.12+ 支持 `keyLocator`:它接收头部,返回对应的密钥。再配合一个在密钥轮换时刷新的 JWKS。
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.
统一 HMAC 密钥的字节
确定配置中的密钥是文本还是 base64,并让每一方都以相同的方式得出密钥。HMAC 计算器会显示两种解读方式下的 MAC。
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")));