← 中文资源

简体中文版本

JWT 签名无效 — 密钥、算法与密钥轮换

解码 JWT 头部,按声明算法与密钥独立验签,检查 kid 轮换与传输截断,定位 invalid signature 的真实原因。

阅读英文原文

现象

报 JsonWebTokenError: invalid signature;网关日志显示 JWT 签名验证失败;一个服务签发的令牌被另一个服务拒绝;本地验证正常,发布或密钥轮换后失败。

诊断

先只解码头部,确认算法与密钥标识:

node -e "const [h]=process.argv[1].split('.'); console.log(JSON.parse(Buffer.from(h,'base64url')));" "$TOKEN"
node -e "const jwt=require('jsonwebtoken'); console.log(jwt.verify(process.argv[1], process.env.JWT_SECRET, { algorithms: ['HS256'] }));" "$TOKEN"

alg 与 kid 决定签发方使用的算法和密钥;缺少 kid 会让轮换无法定位。用明确算法验证成功,说明密钥与签名一致;失败则可能是密钥不同、令牌被截断或签名输入被修改。RS256 可脱离应用独立校验:

node -e "const fs=require('fs'); const [h,p,s]=process.argv[1].split('.');
fs.writeFileSync('signing.txt', h+'.'+p);
fs.writeFileSync('sig.bin', Buffer.from(s,'base64url'));" "$TOKEN"
openssl dgst -sha256 -verify public.pem -signature sig.bin signing.txt

输出 Verified OK 说明令牌与公钥匹配,问题在验证器配置。

修复

确保签发与验证双方使用同一密钥和算法;验证时用 algorithms 白名单锁定算法,绝不信任头部 alg;通过 JWKS 按 kid 取键,并在遇到未知 kid 时刷新。检查代理是否截断超长头部,密钥通过配置管理同步而不是从日志复制。不要用 decode 跳过验签,也不要忽略过期时间来“解决”错误。

验证

用生产密钥和严格算法验证签发方令牌应成功,用另一把密钥签名的令牌必须失败;轮换密钥后签发带新 kid 的令牌,确认验证器能拉取新键并接受,同时旧算法仍被拒绝。