在Go语言中如何解码JWT令牌?

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

How to decode a JWT token in Go?

问题

我目前正在开发一个Go应用程序。我从客户端接收到一个JWT令牌,我需要解码该令牌并获取相关信息:用户、姓名等。

我正在查看可用于处理JWT令牌的库,我找到了dgrijalva/jwt-go,但我不知道如何以简单的方式实现我的目标。

我有令牌,我需要将信息解码为一个映射或至少一个JSON。我该如何做到这一点?

英文:

I am currently working on a Go application. I receive a JWT token from the client side and I need to decode that token and obtain the relevant information: user, name, etc.

I was checking the libraries that are available to handle JWT tokens and I came down to dgrijalva/jwt-go, but I don't see how to accomplish my goal in a simple way.

I have the token and I need to decode the info into a map or at least a json. How can I do it?

答案1

得分: 53

jwt.ParseWithClaims 函数接受一个 jwt.Claims 接口作为第二个参数。除了基于结构体的自定义声明外,该包还提供了基于 map 的声明,即 jwt.MapClaims

因此,你可以将令牌简单地解码为 MapClaims,例如:

tokenString := "<YOUR TOKEN STRING>"
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
	return []byte("<YOUR VERIFICATION KEY>"), nil
})
// ... 错误处理

// 使用解码后的声明进行操作
for key, val := range claims {
	fmt.Printf("Key: %v, value: %v\n", key, val)
}
英文:

Function jwt.ParseWithClaims accept an interface of jwt.Claims as the second argument. Besides struct-based custom claims, the package also provides map-based claims, i.e. jwt.MapClaims.
So, you can simply decode the token into a MapClaims, e.g.

tokenString := &quot;&lt;YOUR TOKEN STRING&gt;&quot;	
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
	return []byte(&quot;&lt;YOUR VERIFICATION KEY&gt;&quot;), nil
})
// ... error handling

// do something with decoded claims
for key, val := range claims {
	fmt.Printf(&quot;Key: %v, value: %v\n&quot;, key, val)
}

答案2

得分: 24

免责声明:我与该库无关。我只是一个用户,发现它很有用,想分享一下。

现在是2019年。我想建议一个备选库,它在使用JWS和/或JWE处理JWT方面做得非常好。

以下是如何使用该库的几个示例:

import (
    "gopkg.in/square/go-jose.v2/jwt"
    "gopkg.in/square/go-jose.v2"
)
...

var claims map[string]interface{} // 用于存储解析后的令牌的通用映射

// 解码JWT令牌,但不验证签名
token, _ := jwt.ParseSigned(tokenString)
_ = token.UnsafeClaimsWithoutVerification(&claims)

// 解码JWT令牌并使用JSON Web Keyset验证签名
token, _ := jwt.ParseSigned(tokenString)
jwks := &jose.JSONWebKeySet { // 通常可以从授权服务器公开的端点获取此信息
			Keys: []jose.JSONWebKey { // 仅为示例
				{
					Key: publicKey, 
					Algorithm: jose.RS256, // 应与JWT令牌头中的算法相同
					KeyID: "kid", // 应与JWT令牌头中的KeyID相同
				},
			},
		}
_ = jwt.Claims(jwks, &claims)

请注意,claims可以是一个包含默认JWT字段和令牌内部的自定义字段的结构体。
例如:

import (
    "github.com/mitchellh/mapstructure"
	"gopkg.in/square/go-jose.v2/jwt"
)
...
type CustomClaims struct {
	*jwt.Claims
	// 除了标准声明之外的其他声明
	extra map[string]interface{}
}

func (cc *CustomClaims) UnmarshalJSON(b []byte) error {
	var rawClaims map[string]interface{}
	if err := json.Unmarshal(b, &rawClaims); err != nil {
		return nil
	}
	var claims jwt.Claims
	var decoderResult mapstructure.Metadata
	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		Result:   &claims,
		Metadata: &decoderResult,
		TagName:  "json",
	})
	if err != nil {
		return err
	}
	if err := decoder.Decode(rawClaims); err != nil {
		return err
	}
	cc.Claims = &claims
	cc.extra = make(map[string]interface{})
	for _, k := range decoderResult.Unused {
		cc.extra[k] = rawClaims[k]
	}
	return nil
}

我还构建了一个命令行工具,它使用该库执行各种编码/解码操作。这也可能是使用该库的一个有用参考。

英文:

Disclaimer: I am not affiliated to the library. I'm just a user, find it useful and would like to share.

It's 2019. I would like to suggest an alternate library that does pretty good job on JWT using JWS and/or JWE.

Here is the few examples on how to use the library:

import (
    &quot;gopkg.in/square/go-jose.v2/jwt&quot;
    &quot;gopkg.in/square/go-jose.v2&quot;
)
...

var claims map[string]interface{} // generic map to store parsed token

// decode JWT token without verifying the signature
token, _ := jwt.ParseSigned(tokenString)
_ = token.UnsafeClaimsWithoutVerification(&amp;claims)

// decode JWT token and verify signature using JSON Web Keyset
token, _ := jwt.ParseSigned(tokenString)
jwks := &amp;jose.JSONWebKeySet { // normally you can obtain this from an endpoint exposed by authorization server
			Keys: []jose.JSONWebKey { // just an example
				{
					Key: publicKey, 
					Algorithm: jose.RS256, // should be the same as in the JWT token header
					KeyID: &quot;kid&quot;, // should be the same as in the JWT token header
				},
			},
		}
_ = jwt.Claims(jwks, &amp;claims)

Noted that, claims can be a struct that contains default JWT fields and also customized fields that are inside the token
E.g.:

import (
    &quot;github.com/mitchellh/mapstructure&quot;
	&quot;gopkg.in/square/go-jose.v2/jwt&quot;
)
...
type CustomClaims struct {
	*jwt.Claims
	// additional claims apart from standard claims
	extra map[string]interface{}
}

func (cc *CustomClaims) UnmarshalJSON(b []byte) error {
	var rawClaims map[string]interface{}
	if err := json.Unmarshal(b, &amp;rawClaims); err != nil {
		return nil
	}
	var claims jwt.Claims
	var decoderResult mapstructure.Metadata
	decoder, err := mapstructure.NewDecoder(&amp;mapstructure.DecoderConfig{
		Result:   &amp;claims,
		Metadata: &amp;decoderResult,
		TagName:  &quot;json&quot;,
	})
	if err != nil {
		return err
	}
	if err := decoder.Decode(rawClaims); err != nil {
		return err
	}
	cc.Claims = &amp;claims
	cc.extra = make(map[string]interface{})
	for _, k := range decoderResult.Unused {
		cc.extra[k] = rawClaims[k]
	}
	return nil
}

I also built a command line tool that uses the library to perform various encoding/decoding activities. It might be a useful reference on the usage of the library too.

答案3

得分: 17

由于问题和答案都提到了JWT库github.com/dgrijalva/jwt-go,请注意这个库已经很久没有维护了

截至2021年6月,有一个社区分支golang-jwt/jwt,由原作者Dave Grijalva正式认可

这也意味着库的导入路径已经改变。请注意,<strike>当前</strike>主要版本v3不支持Go模块,因此在你的go.mod中仍然会看到v3.x.x+incompatible

<hr>

编辑:自2021年8月起,golang-jwt/jwtv4版本可用。这个版本终于支持Go模块。新版本与之前的版本向后兼容,因此只需将旧的导入路径替换为:

github.com/golang-jwt/jwt/v4

然后根据需要更新你的模块——详见迁移指南

<hr>

这个分支主要修复了原始库的一个重要安全问题。在修复之前,该库没有正确处理JWT声明中的多个aud,实际上不符合JWT规范。

除此之外,主要的API仍然相同。例如,要使用HMAC验证解析JWT:

	tokenString := /* 原始JWT字符串 */

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return []byte(/* 你的JWT密钥*/), nil
	})
	if err != nil {
		// 处理错误
	}

    // 验证必要的声明
	if !token.Valid {
	    // 处理无效的令牌
	}

要解析带有自定义声明的JWT,你可以定义自己的结构类型并将jwt.StandardClaims嵌入其中:

    type MyClaims struct {
        jwt.StandardClaims
        MyField string `json:"my_field"`
    }

	tokenString := /* 原始JWT字符串 */

    // 将自定义声明传递给解析函数
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return []byte(/* 你的JWT密钥*/), nil
	})

    // 将`Claims`类型断言为适当类型的变量
    myClaims := token.Claims.(*MyClaims)

<hr>

这个库的一个有效替代品是lestrrat-go/jwx。API略有不同,但同样易于使用:

    tokenString := /* 原始JWT字符串 */

    // 解析并验证签名
	tok, err := jwt.Parse(tokenString, jwt.WithVerify(jwa.HS256, []byte(/* 你的JWT密钥 */)))
	if err != nil {
		// 处理错误
	}

    // 验证必要的声明
	if err := jwt.Validate(tok); err != nil {
		// 处理错误
	}
英文:

Since both the question and answers mention the JWT library github.com/dgrijalva/jwt-go, please note that this library has been unmaintained for a long time now.

As of June 2021 there is a community fork golang-jwt/jwt, officially blessed by Dave Grijalva, the original author.

This also means that the library import path has changed. Note that the <strike>current</strike> major version v3 is not on Go modules, therefore you will still see v3.x.x+incompatible in your go.mod.

<hr>

Edit: since August 2021 version v4 of golang-jwt/jwt is available. This finally supports Go modules. The new version is backward-compatible with previous versions, so in order to migrate simply replace the old import path with:

github.com/golang-jwt/jwt/v4

then update your modules as needed — see also the migration guide for details.

<hr>

The fork most notably fixes an important security issue with the original library. Before the fix, the library didn't properly handle multiple aud in the JWT claims, making it actually not compliant with the JWT spec.

Apart from that, the main API is still the same. For example to parse a JWT with HMAC verification:

	tokenString := /* raw JWT string*/

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New(&quot;unexpected signing method&quot;)
		}
		return []byte(/* your JWT secret*/), nil
	})
	if err != nil {
		// handle err
	}

    // validate the essential claims
	if !token.Valid {
	    // handle invalid tokebn
	}

To parse a JWT with custom claims, you can define your own struct type and embed jwt.StandardClaims into it:

    type MyClaims struct {
        jwt.StandardClaims
        MyField string `json:&quot;my_field&quot;`
    }

	tokenString := /* raw JWT string*/

    // pass your custom claims to the parser function
	token, err := jwt.ParseWithClaims(tokenString, &amp;MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New(&quot;unexpected signing method&quot;)
		}
		return []byte(/* your JWT secret*/), nil
	})

    // type-assert `Claims` into a variable of the appropriate type
    myClaims := token.Claims.(*MyClaims)

<hr>

A valid alternative to this library is lestrrat-go/jwx. The API is slightly different, but also very easy to use:

    tokenString := /* raw JWT string*/

    // parse and verify signature
	tok, err := jwt.Parse(tokenString, jwt.WithVerify(jwa.HS256, []byte(/* your JWT secret */)))
	if err != nil {
		// handle err
	}

    // validate the essential claims
	if err := jwt.Validate(tok); err != nil {
		// handle err
	}

答案4

得分: 8

如果你想从JWT令牌中获取声明而不进行验证,可以使用以下代码:

import "github.com/dgrijalva/jwt-go"
...
token, err := jwt.Parse(tokenStr, nil)
if token == nil {
    return nil, err
}
claims, _ := token.Claims.(jwt.MapClaims)
// claims实际上是一个map[string]interface{}

注意:代码将tokennil进行比较,而不是errerr将是keyFunc can't be nil

英文:

If you want to get claims from jwt token without validation

import &quot;github.com/dgrijalva/jwt-go&quot;
...
	token, err := jwt.Parse(tokenStr, nil)
	if token == nil {
		return nil, err
	}
	claims, _ := token.Claims.(jwt.MapClaims)
    // claims are actually a map[string]interface{}

Note: code compares token with nil, not the err. The err will be keyFunc can&#39;t be nil.

答案5

得分: 2

使用github.com/dgrijalva/jwt-go Go库进行实现。我们可以根据以下方式从API请求中提取JWT令牌信息。

当使用POST请求发送JWT令牌时,您必须在路由部分提取JWT信息。

func RequireTokenAuthentication(inner http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token, err := jwt.ParseFromRequest(
            r,
            func(token *jwt.Token) (interface{}, error) {
                return VERIFICATION.PublicKey, nil
            })
        if err != nil || !token.Valid {
            log.Debug("Authentication failed " + err.Error())
            w.WriteHeader(http.StatusForbidden)
            return
        } else {
            r.Header.Set("username", token.Claims["username"].(string))
            r.Header.Set("userid", strconv.FormatFloat((token.Claims["userid"]).(float64), 'f', 0, 64))
        }
        inner.ServeHTTP(w, r)
    })
}

VERIFICATION.PublicKey:验证密钥(从系统中的public.key文件获取公钥)

如果有任何问题,请告诉我。我可以提供帮助。

英文:

Use github.com/dgrijalva/jwt-go go liabary for the implementation. we can extract JWT token information from the api request according to the following way.

When post the JWT token from the using post request. you must extract JWT information in routing section.

  func RequireTokenAuthentication(inner http.Handler) http.Handler {
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    		token, err := jwt.ParseFromRequest(
    			r,
    			func(token *jwt.Token) (interface{}, error) {
    				return VERIFICATION.PublicKey, nil
    			})
    		if err != nil || !token.Valid) {
    			log.Debug(&quot;Authentication failed &quot; + err.Error())
    			w.WriteHeader(http.StatusForbidden)
    			return
    		} else {
    			r.Header.Set(&quot;username&quot;, token.Claims[&quot;username&quot;].(string))
    			r.Header.Set(&quot;userid&quot;, strconv.FormatFloat((token.Claims[&quot;userid&quot;]).(float64), &#39;f&#39;, 0, 64))
    		}
    		inner.ServeHTTP(w, r)
    	})
    }

VERIFICATION.PublicKey : The key for verification(get public key from public.key file in your system)

Any Issue happen.Please let me know. I can give you help.

huangapple
  • 本文由 发表于 2017年7月31日 07:18:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/45405626.html
匿名

发表评论

匿名网友

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

确定