Go Jwt

参考 https://juejin.cn/post/7342766597243207715?searchId=20240903160011693AB9B562C29F25EA11 http://ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

JWT 介紹

JWT 即 JSON web Token ,用於在網絡應用環境中安全地傳遞聲明claims JWT 是一种紧凑且自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。由于其信息是经过数字签名的,所以可以确保发送的数据在传输过程中未被篡改。

https://blog.meowrain.cn/api/i/2024/09/03/wdI5Xd1725364128332492070.webp

JWT 組成結構

JWT 由三个部分组成,它们之间用 . 分隔,格式如下:Header.Payload.Signature、

  1. Header: Header部分用於描述該JWT的基本信息,比如其類型和所使用的算法 https://blog.meowrain.cn/api/i/2024/09/03/k631TY1725365493091567089.webp

  2. Payload(負載): Payload部分包含所傳遞的聲明,聲明是關於實體和其他數據的語句。聲明能分爲三種類型: > 注冊聲明 > 公共聲明 > 私有聲明 > https://blog.meowrain.cn/api/i/2024/09/03/VnzxGn1725364594070133624.webp

  3. Signature(簽名): 为了防止数据篡改,将头部和负载的信息进行一定算法处理,加上一个密钥,最后生成签名。如果使用的是 HMAC SHA256 算法,那么签名就是将编码后的头部、编码后的负载拼接起来,通过密钥进行HMAC SHA256 运算后的结果。

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

實際使用

安裝go-jwt

1
go get -u github.com/golang-jwt/jwt/v5

创建 Token(JWT) 对象

生成 JWT 字符串首先需要创建 Token 对象(代表着一个 JWT)。因此我们需要先了解如何创建 Token 对象。

jwt 库主要通过两个函数来创建 Token 对象:NewWithClaims 和 New。

jwt.NewWithClaims 函数用于创建一个 Token 对象,该函数允许指定一个签名方法和一组声明claims)以及可变参数 TokenOption。下面是该函数的签名:

https://blog.meowrain.cn/api/i/2024/09/03/u6Pyvc1725365425129935495.webp

https://blog.meowrain.cn/api/i/2024/09/03/cH5kcc1725365807685517628.webp

我們看源碼,可以發現它接受method->加密算法,claims->負載中的聲明部分,opts是TokenOption,返回一個Token對象的指針,這個函數給Token賦值了Method,Header也就是jwt的頭部,還有Claims負載中的聲明部分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Token represents a JWT Token.  Different fields will be used depending on
// whether you're creating or parsing/verifying a token.
type Token struct {
	Raw       string                 // Raw contains the raw token.  Populated when you [Parse] a token
	Method    SigningMethod          // Method is the signing method used or to be used
	Header    map[string]interface{} // Header is the first segment of the token in decoded form
	Claims    Claims                 // Claims is the second segment of the token in decoded form
	Signature []byte                 // Signature is the third segment of the token in decoded form.  Populated when you Parse a token
	Valid     bool                   // Valid specifies if the token is valid.  Populated when you Parse/Verify a token
}

以上為Token對象

我們再研究一下TokenOptions是什麽東西

1
2
3
// TokenOption is a reserved type, which provides some forward compatibility,
// if we ever want to introduce token creation-related options.
type TokenOption func(*Token)

這段註解說明了 TokenOption 是一個保留的類型,其主要目的是提供前向相容性。如果將來在你的系統中需要引入與 token 相關的選項或配置,這個類型可以幫助你更方便地進行擴展或修改。

換句話說,現在即使 TokenOption 可能沒有具體的用途或屬性,它仍然被保留在系統中,以便未來添加新功能時不會影響現有的代碼或結構。這是一種常見的設計模式,用來確保系統在將來需要添加新功能或更改現有功能時,可以保持相對穩定和靈活。

好,現在我們就用這個函數創建一個Token對象吧(這裏其實已經創建好Header.Payload 部分了

MapClaims 是 Go 言語中的一個類型,通常用於處理 JSON Web Token (JWT) 的聲明(claims)。在 jwt-go 庫中,MapClaims 是一個基於地圖(map[string]interface{})的結構,用來存儲和處理 JWT 的聲明。

1
2
3
// MapClaims is a claims type that uses the map[string]interface{} for JSON
// decoding. This is the default claims type if you don't supply one
type MapClaims map[string]interface{}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func main() {
	mapClaims := jwt.MapClaims{
		"iss": "meowrain",
		"sub": "jwt_learn",
		"aud": "public",
	}
	var token *jwt.Token = jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
	fmt.Println(token)
}

https://blog.meowrain.cn/api/i/2024/09/03/7Qm9Ap1725366331610217551.webp


我們還能用jwt.New()函數創建Token對象

我們來看一下源碼,可以看到這個函數只接受一個簽名算法,一個TokenOption,然後調用了我們前面説的NewWithClaims函數,但是在clamis參數上傳遞了一個空的MapClaimsal

1
2
3
4
5
// New creates a new [Token] with the specified signing method and an empty map
// of claims. Additional options can be specified, but are currently unused.
func New(method SigningMethod, opts ...TokenOption) *Token {
	return NewWithClaims(method, MapClaims{}, opts...)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func main() {
	mapClaims := jwt.MapClaims{
		"iss": "meowrain",
		"sub": "jwt_learn",
		"aud": "public",
	}
	var token *jwt.Token = jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
	var token2 *jwt.Token = jwt.New(jwt.SigningMethodHS256)
	token2.Claims = mapClaims
	fmt.Println(token)
	fmt.Println(token2)
}

https://blog.meowrain.cn/api/i/2024/09/03/cglznD1725366858839761550.webp

生成JWT字符串

通过使用 jwt.Token 对象的 SignedString 方法,我们能够对 JWT 对象进行序列化和签名处理,以生成最终的 token 字符串。该方法的签名如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

// SignedString creates and returns a complete, signed JWT. The token is signed
// using the SigningMethod specified in the token. Please refer to
// https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types
// for an overview of the different signing methods and their respective key
// types.
func (t *Token) SignedString(key interface{}) (string, error) {
	sstr, err := t.SigningString()
	if err != nil {
		return "", err
	}

	sig, err := t.Method.Sign(sstr, key)
	if err != nil {
		return "", err
	}

	return sstr + "." + t.EncodeSegment(sig), nil
}

这个函数接收一个key,类型为byte数组,我们传递进去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func main() {
	mapClaims := jwt.MapClaims{
		"iss": "meowrain",
		"sub": "jwt_learn",
		"aud": "public",
	}
	var token *jwt.Token = jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
	jwt_str, err := token.SignedString([]byte("chat"))
	if err != nil {
		panic(err)
	}
	fmt.Println(token)
	fmt.Println(jwt_str)
}

https://blog.meowrain.cn/api/i/2024/09/03/a9suWR1725368096508864467.webp


JWT解析

jwt 库主要通过两个函数来解析 jwt 字符串:Parse 和 ParseWithClaims。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Parse parses, validates, verifies the signature and returns the parsed token.
// keyFunc will receive the parsed token and should return the cryptographic key
// for verifying the signature. The caller is strongly encouraged to set the
// WithValidMethods option to validate the 'alg' claim in the token matches the
// expected algorithm. For more details about the importance of validating the
// 'alg' claim, see
// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
	return NewParser(options...).Parse(tokenString, keyFunc)
}

我們來看看keyFunc這個結構體組成

1
2
3
4
5
6
7
8
9

// Keyfunc will be used by the Parse methods as a callback function to supply
// the key for verification.  The function receives the parsed, but unverified
// Token.  This allows you to use properties in the Header of the token (such as
// `kid`) to identify which key to use.
//
// The returned interface{} may be a single key or a VerificationKeySet containing
// multiple keys.
type Keyfunc func(*Token) (interface{}, error)

可以看到這個函數是一個解析的回調函數,提供key來進行驗證


通用代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
	"errors"
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func GenerateJwt(claims *jwt.MapClaims, key string) (string, error) {
	var token *jwt.Token = jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	jwt_str, err := token.SignedString([]byte(key))
	if err != nil {
		return "", err
	}
	return jwt_str, nil
}
func ParseJwt(key []byte, jwtStr string, options ...jwt.ParserOption) (jwt.Claims, error) {
	token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
		return key, nil
	}, options...)
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, errors.New("invalid token")
	}
	return token.Claims, nil
}
func main() {
	mapClaims := jwt.MapClaims{
		"iss": "meowrain",
		"sub": "jwt_learn",
		"aud": "public",
	}
	var token *jwt.Token = jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
	jwt_str, err := token.SignedString([]byte("chat"))
	if err != nil {
		panic(err)
	}
	fmt.Println(token)
	fmt.Println(jwt_str)
	claims, err := ParseJwt([]byte("chat"), jwt_str)
	if err != nil {
		panic(err)
	}
	fmt.Println(claims)
}

https://blog.meowrain.cn/api/i/2024/09/03/eaKFQB1725374806423837682.webp


相关内容

0%