使用jwt-go进行令牌解析。

huangapple go评论87阅读模式
英文:

go and parsing token with jwt-go

问题

有人能告诉我为什么以下代码(来自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示例代码,但始终遇到相同的错误。

英文:

Could anyone tell me why the following (from https://github.com/dgrijalva/jwt-go) example doesn't work?

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

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

I get an error saying cannot use func literal (type func(*jwt.Token) ([]byte, error)) as type jwt.Keyfunc in argument to jwt.Parse

I've tried to use the code from couple of different jwt-go examples but always ended up with this same error.

答案1

得分: 9

Parse 函数期望的是一个类型为 Keyfunc 的函数,该函数的定义如下:

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

在你的函数字面量中,你需要返回 interface{},而不是 byte[]。你可以使用 byte.Buffer 来包装 byte[],然后可以像在“将任意的 Golang 接口转换为字节数组”中那样读取它。

Gert Cuykensissue 36 的评论中指出:commit e1571c8 应该已经更新了示例代码。
像这个 gist 中的其他示例 也需要更新。

英文:

The function Parse expects

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

You need to return interface{}, not byte[] in your function literal.
(maybe using a byte.Buffer to wrap the byte[], that you can then read as in "Convert arbitrary Golang interface to byte array")

Gert Cuykens points out in the comments to issue 36: commit e1571c8 should have updated the example.
Other examples like this gist also need to be updated.

答案2

得分: 3

如另一个答案中提到的那样,最新版本的github.com/dgrijalva/jwt-gov3.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 Go结构。

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

对于XY,它们都是*big.Int。如果您有这些整数的字节,可以创建一个新的*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本身,
但确定它与哪个公钥相关联的最佳方法是使用kidkid是包含在JWT头中的字符串值。请阅读有关RFC中的kid参数的说明。可以像这样从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键是kidinterface{}值是其公钥。

这是一个示例:

// 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 Key(JWK)和JSON Web Key Set(JWKs)。一些身份提供商,如KeycloakAmazon Cognito(AWS)通过HTTPS端点提供这些信息。

这是一个JWKs的示例:

{
  "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"
    }
  ]
}

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

我以前遇到过这种用例,并创建了一个专门用于消费JWK并创建jwt.Keyfunc的Go包。如果符合您的用例,请查看这里的存储库:github.com/MicahParks/keyfunc

英文:

As mentioned in another answer, the most recent version of is github.com/dgrijalva/jwt-go is v3.2.0+incompatible.
The docs are now outdated as the package's jwt.Keyfunc
function now has this signature:

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

When parsing JWTs, this jwt package also authenticates them. Two things are required to authenticate JWTs.

  1. The JWT itself.
  2. The cryptographic key that was used to sign the JWT. Typically, this is a public key.

This is where the jwt.Keyfunc fits in. The return value
of interface{} is later type asserted to a cryptographic key. For many use cases, this will be an RSA or ECDSA based
algorithm. This means the return type is typically *ecdsa.PublicKey
or *rsa.PublicKey. (HMAC keys can also be used, but I won't cover that
use case.)

If your public keys are in PEM format, you might be interested in using the functions built into the jwt
package: ParseECPublicKeyFromPEM
and ParseRSAPublicKeyFromPEM
.

Creating a public key data structure

If your public keys are in another format, you may need to build
the *ecdsa.PublicKey
and *rsa.PublicKey Go structs by populating their exported fields.

ECDSA

Here's the data structure to fill:

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

The embedded elliptic.Curve is created by using the crypto/elliptic function associated with its length:

// 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

For both X and Y, these are *big.Ints. If you have the bytes for these integers, creating a new *big.Int and
using the SetBytes method.

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

RSA

Here's the data structure to fill:

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

The modulus, N is a *big.Int. If you have the bytes for this integer, create a new *big.Int and using the
SetBytes method.

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

The exponent is a regular integer. So that should be strait forward, but if you have the bytes for this integer instead
you can create an integer like this:

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

Create a jwt.Keyfunc

Now that the public keys are in the right data strucuture, it's time to create
a jwt.Keyfunc. As mentioned before, the
input for this function will be
a *jwt.Token and the output will be either
an *ecdsa.PublicKey or a
*rsa.PublicKey, but typed as an empty interface: interface{}, or
an error.

The *jwt.Token contains the JWT itself,
but the best way to identify which public key it's associated with is the kid. The kid is a string value contained
in the JWT header. Read about the
kid parameter in the RFC. It can be read from the JWT
like this:

// 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)
}

At this point, the input is a kid and the output is the public key. The simplest implementation of a
jwt.Keyfunc at this point is a function
that reads from a map[string]interface{} where the key string is a kid and value interface{} is its public key.

Here's an example:

// 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
}

You can now use the created keyFunc function-as-a-variable when parsing JWTs.

// 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.

Other JWT public key formats

JWTs' public keys can also be described by RFC 7517. This RFC describes
a JSON Web Key (JWK) and JSON Web Key Set (JWKs). Some identity providers, like Keycloak
or Amazon Cognito (AWS) provide these via HTTPS endpoints.

Here's an example JWKs:

{
  "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"
    }
  ]
}

In the above example, a *rsa.PublicKey can be created from just the
n and e JSON attributes. It's kid attribute can be used to create an entry in the keyMap described in an earlier
section.

I've actually run into this use case before and created a Go package just for consuming JWKs and creating
jwt.Keyfunc. If this fits your use case,
check out the repository here: github.com/MicahParks/keyfunc.

huangapple
  • 本文由 发表于 2014年9月15日 20:31:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/25848215.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定