【问题标题】:go and parsing token with jwt-go使用 jwt-go 解析令牌
【发布时间】:2014-11-08 23:45:04
【问题描述】:

谁能告诉我为什么下面的(来自https://github.com/dgrijalva/jwt-go)的例子不起作用?

token, err := jwt.Parse(myToken, func(token *jwt.Token) ([]byte, error) {
    return myLookupKey(token.Header["kid"])
})

if err == nil && token.Valid {
    deliverGoodness("!")
} else {
    deliverUtterRejection(":(")
}

我收到一条错误消息,提示 cannot use func literal (type func(*jwt.Token) ([]byte, error)) as type jwt.Keyfunc in argument to jwt.Parse

我尝试使用来自几个不同 jwt-go 示例的代码,但总是以同样的错误告终。

【问题讨论】:

    标签: go jwt jwt-go


    【解决方案1】:

    正如另一个答案中提到的,最新版本是github.com/dgrijalva/jwt-go 是v3.2.0+incompatible。 文档现在已过时,因为包的 jwt.Keyfunc 函数现在有这个签名:

    type Keyfunc func (*Token) (interface{}, error)
    

    在解析 JWT 时,这个 jwt 包也会对它们进行身份验证。验证 JWT 需要两件事。

    1. JWT 本身。
    2. 用于签署 JWT 的加密密钥。通常,这是一个公钥。

    这就是 jwt.Keyfunc 适合的地方。返回值 interface{} 的类型后来被断言为加密密钥。对于许多用例,这将是基于 RSA 或 ECDSA 算法。这意味着返回类型通常是*ecdsa.PublicKey 或*rsa.PublicKey。 (也可以使用 HMAC 密钥,但我不会介绍 用例。)

    如果您的公钥是 PEM 格式,您可能有兴趣使用 jwt 中内置的功能 包:ParseECPublicKeyFromPEM 和ParseRSAPublicKeyFromPEM .

    创建公钥数据结构

    如果您的公钥是其他格式,您可能需要构建 *ecdsa.PublicKey 和 *rsa.PublicKey 通过填充导出的字段来转到结构。

    ECDSA

    这是要填充的数据结构:

    // PublicKey represents an ECDSA public key.
    type PublicKey struct {
        elliptic.Curve
        X, Y *big.Int
    }
    

    嵌入的elliptic.Curve 是使用与其长度相关联的crypto/elliptic 函数创建的:

    // Create the ECDSA public key.
    publicKey = &ecdsa.PublicKey{}
    
    // Set the curve type.
    var curve elliptic.Curve
    switch myCurve {
        case p256:
            curve = elliptic.P256()
        case p384:
            curve = elliptic.P384()
        case p521:
            curve = elliptic.P521()
    }
    publicKey.Curve = curve
    

    对于X 和Y,它们都是*big.Ints。如果您有这些整数的字节,请创建一个新的 *big.Int 和 使用SetBytes 方法。

    publicKey.X = big.NewInt(0).SetBytes(xCoordinate)
    publicKey.Y = big.NewInt(0).SetBytes(yCoordinate)
    

    RSA

    这是要填充的数据结构:

    // A PublicKey represents the public part of an RSA key.
    type PublicKey struct {
        N *big.Int // modulus
        E int      // public exponent
    }
    

    模数N 是*big.Int。如果您有此整数的字节,请创建一个新的 *big.Int 并使用 SetBytes 方法。

    publicKey.N = big.NewInt(0).SetBytes(modulus)
    

    指数是一个常规整数。所以这应该是海峡前进,但如果你有这个整数的字节 你可以像这样创建一个整数:

    publicKey.E = int(big.NewInt(0).SetBytes(exponent).Uint64())
    

    创建一个jwt.Keyfunc

    现在公钥已经在正确的数据结构中,是时候创建了 jwt.Keyfunc。如前所述, 此功能的输入将是 *jwt.Token 并且输出将是 *ecdsa.PublicKey 或 *rsa.PublicKey,但键入为空接口:interface{},或 error。

    *jwt.Token 包含 JWT 本身, 但识别与哪个公钥相关联的最佳方法是kid。 kid 是包含的字符串值 在 JWT 标头中。阅读有关 kid parameter in the RFC。可以从 JWT 中读取 像这样:

    // ErrKID indicates that the JWT had an invalid kid.
    ErrKID := errors.New("the JWT has an invalid kid")
    
    // Get the kid from the token header.
    kidInter, ok := token.Header["kid"]
    if !ok {
        return nil, fmt.Errorf("%w: could not find kid in JWT header", ErrKID)
    }
    kid, ok := kidInter.(string)
    if !ok {
        return nil, fmt.Errorf("%w: could not convert kid in JWT header to string", ErrKID)
    }
    

    此时,输入是kid,输出是公钥。最简单的实现 jwt.Keyfunc此时是一个函数 从map[string]interface{} 读取,其中键string 是kid,值interface{} 是它的公钥。

    这是一个例子:

    // ErrKID indicates that the JWT had an invalid kid.
    ErrKID := errors.New("the JWT has an invalid kid") // TODO This should be exported in the global scope.
    
    // Create the map of KID to public keys.
    keyMap := map[string]interface{}{"zXew0UJ1h6Q4CCcd_9wxMzvcp5cEBifH0KWrCz2Kyxc": publicKey}
    
    // Create a mutex for the map of KID to public keys.
    var mux sync.Mutex
    
    // Create the jwt.Keyfunc
    var keyFunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {
    
        // Get the kid from the token header.
        kidInter, ok := token.Header["kid"]
        if !ok {
            return nil, fmt.Errorf("%w: could not find kid in JWT header", ErrKID)
        }
        kid, ok := kidInter.(string)
        if !ok {
            return nil, fmt.Errorf("%w: could not convert kid in JWT header to string", ErrKID)
        }
    
        // Get the appropriate public key from the map of KID to public keys.
        mux.Lock()
        publicKey, ok := keyMap[kid]
        mux.Unlock()
        if !ok {
            return nil, fmt.Errorf("%w: could not find a matching KID in the map of keys", ErrKID)
        }
    
        return publicKey, nil
    }
    

    您现在可以在解析 JWT 时使用创建的 keyFunc 函数作为变量。

    // Parse the JWT.
    token, err := jwt.Parse(myToken, keyFunc)
    if err != nil {
        log.Fatalf("Failed to parse JWT.\nError: %s\n", err.Error())
    }
    
    // Confirm the JWT is valid.
    if !token.Valid {
        log.Fatalln("JWT failed validation.")
    }
    
    // TODO Proceed with authentic JWT.
    

    其他 JWT 公钥格式

    JWT 的公钥也可以用RFC 7517 来描述。本 RFC 描述 JSON Web 密钥 (JWK) 和 JSON Web 密钥集 (JWK)。一些身份提供者,例如Keycloak 或 Amazon Cognito (AWS) 通过 HTTPS 端点提供这些。

    这是一个 JWK 示例:

    {
      "keys": [
        {
          "kid": "zXew0UJ1h6Q4CCcd_9wxMzvcp5cEBifH0KWrCz2Kyxc",
          "kty": "RSA",
          "alg": "PS256",
          "use": "sig",
          "n": "wqS81x6fItPUdh1OWCT8p3AuLYgFlpmg61WXp6sp1pVijoyF29GOSaD9xE-vLtegX-5h0BnP7va0bwsOAPdh6SdeVslEifNGHCtID0xNFqHNWcXSt4eLfQKAPFUq0TsEO-8P1QHRq6yeG8JAFaxakkaagLFuV8Vd_21PGJFWhvJodJLhX_-Ym9L8XUpIPps_mQriMUOWDe-5DWjHnDtfV7mgaOxbBvVo3wj8V2Lmo5Li4HabT4MEzeJ6e9IdFo2kj_44Yy9osX-PMPtu8BQz_onPgf0wjrVWt349Rj6OkS8RxlNGYeuIxYZr0TOhP5F-yEPhSXDsKdVTwPf7zAAaKQ",
          "e": "AQAB",
          "x5c": [
            "MIICmzCCAYMCBgF4HR7HNDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjEwMzEwMTcwOTE5WhcNMzEwMzEwMTcxMDU5WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCpLzXHp8i09R2HU5YJPyncC4tiAWWmaDrVZenqynWlWKOjIXb0Y5JoP3ET68u16Bf7mHQGc/u9rRvCw4A92HpJ15WyUSJ80YcK0gPTE0Woc1ZxdK3h4t9AoA8VSrROwQ77w/VAdGrrJ4bwkAVrFqSRpqAsW5XxV3/bU8YkVaG8mh0kuFf/5ib0vxdSkg+mz+ZCuIxQ5YN77kNaMecO19XuaBo7FsG9WjfCPxXYuajkuLgdptPgwTN4np70h0WjaSP/jhjL2ixf48w+27wFDP+ic+B/TCOtVa3fj1GPo6RLxHGU0Zh64jFhmvRM6E/kX7IQ+FJcOwp1VPA9/vMABopAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALILq1Z4oQNJZEUt24VZcvknsWtQtvPxl3JNcBQgDR5/IMgl5VndRZ9OT56KUqrR5xRsWiCvh5Lgv4fUEzAAo9ToiPLub1SKP063zWrvfgi3YZ19bty0iXFm7l2cpQ3ejFV7WpcdLJE0lapFdPLo6QaRdgNu/1p4vbYg7zSK1fQ0OY5b3ajhAx/bhWlrN685owRbO5/r4rUOa6oo9l4Qn7jUxKUx4rcoe7zUM7qrpOPqKvn0DBp3n1/+9pOZXCjIfZGvYwP5NhzBDCkRzaXcJHlOqWzMBzyovVrzVmUilBcj+EsTYJs0gVXKzduX5zO6YWhFs23lu7AijdkxTY65YM0="
          ],
          "x5t": "IYIeevIT57t8ppUejM42Bqx6f3I",
          "x5t#S256": "TuOrBy2NcTlFSWuZ8Kh8W8AjQagb4fnfP1SlKMO8-So"
        }
      ]
    }
    

    在上面的示例中,*rsa.PublicKey 可以仅从 n 和 e JSON 属性。它的kid 属性可用于在前面描述的keyMap 中创建一个条目 部分。

    我之前确实遇到过这个用例,并创建了一个 Go 包,仅用于使用 JWK 和创建 jwt.Keyfunc。如果这适合您的用例, 在此处查看存储库:github.com/MicahParks/keyfunc。

    【讨论】:

      【解决方案2】:

      Parse 期望的函数

      type Keyfunc func(*Token) (interface{}, error)
      

      您需要在函数字面量中返回 interface{},而不是 byte[]。
      (也许使用byte.Buffer 来包装byte[],然后您可以将其读取为“Convert arbitrary Golang interface to byte array”)

      Gert Cuykens 在 cmets 中指出 issue 36:commit e1571c8 应该更新了示例。
      Other examples like this gist 也需要更新。

      【讨论】:

      • 好吧,我会……就是这样,现在一切似乎都按预期工作了。最有趣的是,在 jwt-go 的 Github 中,他们在示例中使用了 []byte。谢谢!
      • 请注意,对界面的更改是最近才发生的:github.com/dgrijalva/jwt-go/commit/… 可能想要 ping 作者以修复他们的文档。
      • @GertCuykens 好的错误报告。我已将其包含在答案中以提高知名度。
      猜你喜欢
      • 1970-01-01
      • 2017-10-21
      • 2019-12-25
      • 2017-03-20
      • 2019-02-06
      • 2018-11-29
      • 1970-01-01
      • 2020-06-14
      • 2021-12-30
      相关资源
      最近更新 更多