错误信息
invalid signature
含义
令牌解析正常,结构也有效,但密码学检查失败了:用你提供的密钥无法验证针对 header.payload 的签名。要么这个密钥不是给令牌签名的那个,要么被签名的字节在签名之后发生了变化。
原因
用错了验证密钥
常见这是最常见的情况,远多于其他原因:环境中的密钥与颁发者使用的不是同一个——轮换后已过时的公钥、用预发布环境的密钥验证生产环境的令牌,或者验证方固定了一个密钥而颁发者用另一个签名,导致 JWKS 的 kid 不匹配。
手写的签名代码签错了字节
偶见手工构造令牌的代码必须用 SHA-256 对 base64url(header).base64url(payload) 的 ASCII 字节签名,并输出 RFC 7518 为 ES256 规定的 64 字节原始 r‖s。对 JSON 本身签名、对标准 base64 编码的段签名,或在 ES256 头部下对 SHA-384 摘要签名,得到的签名格式正确,却永远验证不通过。(DER 编码的签名在检查之前就会被拒绝,报错为“"ES256" signatures must be "64" bytes”;被环境变量弄乱的 PEM 根本不会被当作密钥接受,会报错“secretOrPublicKey must be an asymmetric key when using ES256”。)
令牌在签名后被改动
偶见代理、日志组件或客户端重新序列化了载荷 JSON,或者改动了其中的字符。头部或载荷中哪怕只改了一个字符,签名也会失效——这正是签名的意义所在。(给某一段加上 Base64 填充,会在更早一步失败,报错“invalid token”;被截断的令牌会报错“jwt malformed”或签名长度错误。)
修复方法
- 1.
在两端计算密钥指纹
对验证方实际加载的公钥计算哈希,并与颁发者的公钥对比。指纹不同就说明密钥已过时或用错了——这是最常见的原因,远多于其他——排查一步即可结束。
js import { createHash, createPublicKey } from "node:crypto"; // Run on the verifier, and on the issuer with its signing key: the outputs must match. const spki = createPublicKey(process.env.JWT_PUBLIC_KEY).export({ type: "spki", format: "der" }); console.log(createHash("sha256").update(spki).digest("hex")); - 2.
让令牌的 kid 与验证密钥对应
先不验证签名,只解码头部,把其中的 kid 与你手上的密钥对比。如果颁发者发布了 JWKS,就按 kid 查找密钥,而不是硬编码一个密钥。
js const { header } = jwt.decode(token, { complete: true }); console.log(header.kid, header.alg); // compare with your JWKS / configured key - 3.
让调试器指出哪里不匹配
把令牌和公钥粘贴到 JWT 调试器中:它会在本地验证 ES256/ES384/ES512,并区分密钥错误、载荷被篡改和签名编码格式错误,而不只是给出一个简单的布尔值。