跳到主要内容
ecdsa.com
golang-jwt/jwt (Go)Go

错误信息

token signature is invalid: crypto/ecdsa: verification error

也可能输出为

  • token signature is invalid: crypto/rsa: verification error
  • token signature is invalid: signature is invalid
  • token signature is invalid: ed25519: verification error
  • crypto/ecdsa: verification error

golang-jwt v4 只返回内部文本,例如“crypto/ecdsa: verification error”。应使用 `errors.Is(err, jwt.ErrTokenSignatureInvalid)`(v5),而不是匹配字符串。

golang-jwt/jwt (Go) — jwt.ErrTokenSignatureInvalid joined with the algorithm's verification error

含义

golang-jwt 解析了令牌、接受了它的算法,也从你的 `Keyfunc` 拿到了密钥,但用这个密钥验证签名没有通过。在 v5 中,这个错误由 `jwt.ErrTokenSignatureInvalid`(“token signature is invalid”)和算法自身的错误组合而成:ES* 为 `crypto/ecdsa: verification error`,RS*/PS* 为 `crypto/rsa: verification error`,HS* 为 `signature is invalid`。令牌的字节和这个密钥根本不是一对。

原因

修复方法

  1. 1.

    按 kid 从 JWKS 中查找密钥

    让 Keyfunc 从颁发者的密钥集中选出令牌头部指定的密钥,并在轮换时刷新密钥集(例如使用 `github.com/MicahParks/keyfunc`)。始终用 `WithValidMethods` 固定允许的算法。

    go
    import (
    	"context"
    	"github.com/MicahParks/keyfunc/v3"
    	"github.com/golang-jwt/jwt/v5"
    )
    
    // Resolve the verification key from the issuer's JWKS by the token's kid.
    k, err := keyfunc.NewDefaultCtx(ctx, []string{"https://issuer.example.com/.well-known/jwks.json"})
    if err != nil {
    	return err
    }
    token, err := jwt.Parse(raw, k.Keyfunc,
    	jwt.WithValidMethods([]string{"ES256"}),
    	jwt.WithIssuer("https://issuer.example.com/"),
    	jwt.WithAudience("https://api.example.com"))
  2. 2.

    证明双方使用的是同一个密钥

    对验证方使用的公钥和颁发者私钥对应的公钥,分别计算其 DER 编码的哈希。哈希不一致,问题就查清了。

    bash
    # Same key on both sides? Compare SPKI fingerprints.
    openssl pkey -pubin -in verifier-public.pem -outform DER | sha256sum
    openssl pkey -in issuer-private.pem -pubout -outform DER | sha256sum
  3. 3.

    按错误类别处理,而不是按字符串

    golang-jwt v5 提供了哨兵错误(sentinel error)。把签名验证失败映射为 401,不要退而尝试其他密钥,也不要跳过验证。

    go
    switch {
    case errors.Is(err, jwt.ErrTokenSignatureInvalid):
    	return http.StatusUnauthorized // wrong key or tampered token: never retry with another key blindly
    case errors.Is(err, jwt.ErrTokenExpired):
    	return http.StatusUnauthorized // client should refresh
    case errors.Is(err, jwt.ErrTokenMalformed):
    	return http.StatusBadRequest
    }

相关错误

浏览完整的签名错误库