英文:
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 Cuykens 在 issue 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-go
是v3.2.0+incompatible
。
文档现在已经过时,因为该包的jwt.Keyfunc
函数现在具有以下签名:
type Keyfunc func (*Token) (interface{}, error)
在解析JWT时,这个jwt
包也会对其进行身份验证。要验证JWT,需要两个东西:
- JWT本身。
- 用于签署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
对于X
和Y
,它们都是*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本身,
但确定它与哪个公钥相关联的最佳方法是使用kid
。kid
是包含在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
键是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 Key(JWK)和JSON Web Key Set(JWKs)。一些身份提供商,如Keycloak或Amazon 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"
}
]
}
在上面的示例中,可以仅从n
和e
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.
- The JWT itself.
- 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.Int
s. 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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论