ruby-jwt (Ruby)Ruby
错误信息
Signature verification failed (JWT::VerificationError)
也可能输出为
- JWT::VerificationError: Signature verification failed
异常未被捕获时,Ruby 会先打印出错位置,后面跟着“Signature verification failed (JWT::VerificationError)”;这段文本在 ruby-jwt 2.10 和 3.3 中相同。
ruby-jwt (Ruby) — JWT::VerificationError from JWT.decode
含义
ruby-jwt 解码了令牌,并用你传入的密钥验证其签名,结果验证失败。令牌格式正确,算法也在允许之列;要么密钥不属于签名者,要么令牌被改动过。`JWT::VerificationError` 是 `JWT::DecodeError` 的子类,因此 `rescue JWT::DecodeError` 会把它和令牌格式错误一并捕获。
原因
配置的密钥不是签名者的密钥
常见来自其他环境的公钥、轮换后已过时的密钥,或者每次启动都重新生成的密钥对(这种开发环境的做法被带进了生产环境)。
用 JWKS 中错误的密钥验证
偶见从颁发者的 JWKS 中直接取 `keys.first`,只在颁发者改用另一个密钥签名之前有效。应通过 `jwks:` 选项让 ruby-jwt 按 `kid` 选择密钥。
HMAC 密钥不一致
偶见Rails 应用常常从 `secret_key_base` 派生密钥;另一个服务如果使用了不同的值——或者是同一个值但末尾多了换行符——计算出的 MAC 就会不同。
修复方法
- 1.
按 kid 从颁发者的 JWKS 中选择密钥
`jwks:` 选项接受一个加载器;当令牌指定了未知的密钥时,ruby-jwt 会以 `kid_not_found: true` 再次调用它,因此能获取轮换后的新密钥。
ruby require "jwt" require "net/http" jwks_loader = ->(options) do @jwks = nil if options[:kid_not_found] && @jwks_loaded_at.to_i < Time.now.to_i - 300 # refetch on rotation @jwks ||= begin @jwks_loaded_at = Time.now.to_i JSON.parse(Net::HTTP.get(URI("https://issuer.example.com/.well-known/jwks.json"))) end end payload, header = JWT.decode(token, nil, true, algorithms: ["ES256"], jwks: jwks_loader, iss: "https://issuer.example.com/", verify_iss: true) - 2.
确认密钥确实匹配
两边公钥部分的哈希相同,就可以排除密钥问题;接下来检查令牌是否在传输过程中被修改。
bash openssl pkey -in issuer-private.pem -pubout | sha256sum openssl pkey -pubin -in configured-public.pem | sha256sum # must be identical