44.jwt在go中的使用及原理

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/26-jwt

一、JWT是什么

JWT(JSON Web Token) 是一种基于 JSON 的开放标准,用于在网络应用间传递声明。JWT可安全地将用户身份验证和授权数据作为 JSON 对象在各个应用程序之间传递。目前JWT 是一种非常流行的身份验证和授权机制,被广泛应用于各种互联网应用中。

JWT 主要由三部分组成:Header,Payload 和Signature

  • Header 包含了两部分信息:令牌的类型(即 JWT)和所使用的算法,通常采用的算法是 HMAC SHA256RSA

  • Payload(载荷)包含了要传递的信息,可以包括用户 ID、用户角色、过期时间等信息。payload中一般会包含jwt.StandardClaims,该结构体字段有

iss (issuer)(发行人)
sub (subject)(主题)
aud (audienct) (受众)
exp (expiration time)(到期时间)
nbf (not before)(不早于 生效时间)
iat (issued at)(发布于)
jti (jwt id)(JWT ID)
  • Signature(签名)是使用私钥对 Header Payload 进行签名生成的,用来验证消息确实是由发送方发出的,以及在传输过程中没有被篡改过。

JWT 的优点包括:

  • 无状态:JWT 本身就包含了用户信息,不需要再去查询数据库或者其他的存储设备。
  • 安全性:由于JWT包含了签名,所以一旦JWT被篡改,接收方就能够检测到。
  • 便捷性:JWT 的格式是轻量级的,容易传输,可以通过 URLPOST 参数或者在HTTP header中传递。

使用 JWT 的过程可以分为以下几个步骤:

  • 在服务器端生成一个 JWT,包括 HeaderPayloadSignature
  • 客户端在需要验证用户身份的请求中,将 JWT 添加到 HTTP header 中。
  • 服务器收到请求后,从 HTTP header 中提取 JWT,并对其进行验证,包括签名验证、Payload 中的信息验证等。如果验证成功,服务器返回请求所需的数据。如果验证失败,则拒绝请求。
    在这里插入图片描述

二、go生成jwt

package main

import (
	"github.com/golang-jwt/jwt/v4"
	"time"
)

const (
	ExpireDuration = 3600 * time.Second
	JwtSecretKey   = "abc123"
)

type MyClaims struct {
	Id       int64  `json:"id"`
	Username string `json:"username"`
	jwt.StandardClaims
}

// 生成token
func GenerateToken(id int64, username string) (string, error) {
	// 定义token的过期时间
	expireTime := time.Now().Add(ExpireDuration).Unix()

	// 创建一个自定义的Claims
	myClaims := &MyClaims{
		Id:       id,
		Username: username,
		StandardClaims: jwt.StandardClaims{
			Audience:  "",
			ExpiresAt: expireTime,
			Id:        "",
			IssuedAt:  time.Now().Unix(),
			Issuer:    "lym",
			NotBefore: 0,
			Subject:   "",
		},
	}

	// 使用 JWT 签名算法生成token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)

	// 将token进行加盐加密
	// 注意:该方法参数虽然是interface{},但是必须传入[]byte类型的参数
	tokenString, err := token.SignedString([]byte(JwtSecretKey))
	if err != nil {
		return "", err
	}

	return tokenString, nil

}


详细分析:

type MyClaims struct {
	Id       int64  `json:"id"`
	Username string `json:"username"`
	jwt.StandardClaims
}

该结构体用于定义 JWTpayload,也就是 JWT 载荷中存储的信息。其中,IdUsername 用于标识用户身份,jwt.StandardClaims 则是 JWT 的标准声明,包含了 JWT 的一些基本信息,比如过期时间、签发时间等。在创建 JWT 时,我们需要将这个结构体传入,以便生成 JWT payload。在验证 JWT 时,我们也需要解析出这个结构体,以便获取 JWT 中存储的用户身份信息。

StandardClaims中的信息用于控制JWT token的生命周期和有效性。
ExpiresAt表示JWT token的过期时间,超过这个时间后,JWT token将失效,无法再被使用。IssuedAt表示JWT token的签发时间,Issuer表示JWT token的签发者,这些信息可以帮助验证JWT token的合法性。

注:在MyClaims结构体中通过匿名嵌入jwt.StandardClaims,便是让MyClaims继承了jwt.StandardClaims,从而实现了Claims接口,可以作为 jwt.NewWithClaims函数的第二个参数,如下:

在这里插入图片描述

type Claims interface {
	Valid() error
}
// NewWithClaims creates a new Token with the specified signing method and claims.
func NewWithClaims(method SigningMethod, claims Claims) *Token {
	return &Token{
		Header: map[string]interface{}{
			"typ": "JWT",
			"alg": method.Alg(),
		},
		Claims: claims,
		Method: method,
	}
}

另外一种常见的方式是直接使用jwt.MapClaims,它的定义为type MapClaims map[string]interface{},也实现了Claims接口,使用方式如下

// 生成token
func GenerateToken(id int64, username string) (string, error) {
	

	// 使用 JWT 签名算法生成token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       id,
		"username": username,
	})

	// 将token进行加盐加密
	tokenString, err := token.SignedString(JwtSecretKey)
	if err != nil {
		return "", err
	}

	return tokenString, nil

}

为何要加盐加密?

加盐加密的目的是为了增加数据的安全性,防止攻击者对加密后的数据进行破解。加盐是指在原始数据的基础上加上一些随机的字符串或数字,使得同样的原始数据加盐后的结果是不同的。加密是指将原始数据和盐一起通过某种加密算法进行加密,得到一串密文。密文是不能被破解的,只能通过使用同样的盐和加密算法对原始数据进行加密后得到相同的密文来验证数据的真实性。

JWT中,加盐加密可以有效地防止攻击者伪造token,从而保障应用的安全性。具体地,JWT在生成token时会使用指定的秘钥对payload进行签名,这个秘钥就是加盐的一部分。只有使用相同的秘钥才能对payload进行解密并验证token的真实性。因此,只有知道秘钥的人才能生成有效的token,从而有效地保护了应用的安全性。

jwt中给token加盐的方法有一个注意点:必须传入[]byte类型的参数

// 注意:该方法参数虽然是interface{},但是必须传入[]byte类型的参数
tokenString, err := token.SignedString([]byte(JwtSecretKey))

原因:
进入SignedString()的源码,可以看出SignedString使用SigningMethodHS256方式,结合一个随机值(JwtSecretKey)进行加密,进入t.Method.Sign(sstr, key)中查看,找到SigningMethodHS256所属的SigningMethodHMAC类型函数:

// Implements the Sign method from SigningMethod for this signing method.
// Key must be []byte
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
	if keyBytes, ok := key.([]byte); ok {
		if !m.Hash.Available() {
			return "", ErrHashUnavailable
		}

		hasher := hmac.New(m.Hash.New, keyBytes)
		hasher.Write([]byte(signingString))

		return EncodeSegment(hasher.Sum(nil)), nil
	}

	return "", ErrInvalidKeyType
}

注释中明确说明key必须是[]byte类型。代码中也是只针对[]byte类型进行处理:key.([]byte)。

单元测试

package main

import "testing"

func TestGenerateToken(t *testing.T) {
	token, err := GenerateToken(1, "lym")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf(token)
}

运行之后输出:

稍后验证解析token时就可以直接用本次生成的

=== RUN   TestGenerateToken
    main_test.go:10: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJseW0iLCJleHAiOjE3MDEwMTgzMTMsImlhdCI6MTcwMTAxNDcxMywiaXNzIjoibHltIn0.0YeCw4Nxl7QCXtt_7YaXXVwaUu7UJbGoMAJaEZDYZTA
--- PASS: TestGenerateToken (0.00s)
PASS

Process finished with the exit code 0

在这里插入图片描述

三、go解析jwt

func ParseToken(tokenString string) (*MyClaims, error) {
	// 解析 token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 注意:虽然返回值是interface,但是这里必须返回[]byte类型,否则运行时会报错key is of invalid type
		return []byte(JwtSecretKey), nil
	})
	if err != nil {
		return nil, err
	}

	if myClaims, ok := token.Claims.(*MyClaims); ok && token.Valid {
		return myClaims, nil
	} else {
		return nil, jwt.NewValidationError("invalid token", jwt.ValidationErrorClaimsInvalid)
	}
}

jwt.ParseWithClaims()函数用于解析并验证JWT token,并提取出其中的MyClaims数据结构。该函数接受三个参数:

  • tokenString: 要解析的JWT token字符串。
  • claims: 一个结构体指针,用于接收从JWT token中解析出来的Claims数据,赋值给返回值tokenClaims字段。
// 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                 // The raw token.  Populated when you Parse a token
	Method    SigningMethod          // The signing method used or to be used
	Header    map[string]interface{} // The first segment of the token
	Claims    Claims                 // The second segment of the token
	Signature string                 // The third segment of the token.  Populated when you Parse a token
	Valid     bool                   // Is the token valid?  Populated when you Parse/Verify a token
}
  • keyFunc: 一个回调函数,用于验证JWT token的签名。在函数内部,可以对JWT token签名进行验证,比如检查签名是否正确、是否过期等等。该函数需要返回一个interface{}类型的值,表示用于验证签名的密钥。如果验证成功,应该返回一个非空的密钥;如果验证失败,应该返回一个nil值和相应的错误信息。

注意:虽然keyFunc第一个返回值是interface{},但是这里必须返回[]byte类型,否则运行时会报错key is of invalid type

	// 解析 token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		// 注意:虽然返回值是interface{},但是这里必须返回[]byte类型,否则运行时会报错key is of invalid type
		return []byte(JwtSecretKey), nil
	})

在上述代码中,&MyClaims{}作为第二个参数传递给了jwt.ParseWithClaims()函数,这表示从JWT token中解析出的Claims数据将被解码为该结构体类型。第三个参数是一个匿名函数,用于验证JWT token的签名,JwtSecretKey被作为用于验证签名的密钥传递。如果JWT token验证通过,jwt.ParseWithClaims()函数将返回一个*jwt.Token类型的指针,其中包含了解码后的Claims数据,以及一些其他的元数据。如果验证失败,函数将返回相应的错误信息。

单元测试:

func TestParseToken(t *testing.T) {
	c, err := ParseToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJseW0iLCJleHAiOjE3MDEwMTgzMTMsImlhdCI6MTcwMTAxNDcxMywiaXNzIjoibHltIn0.0YeCw4Nxl7QCXtt_7YaXXVwaUu7UJbGoMAJaEZDYZTA")
	if err != nil {
		t.Fatal(err)
	}
	t.Log(*c)
}

运行后输出:

=== RUN   TestParseToken
    main_test.go:18: {1 lym { 1701018313  1701014713 lym 0 }}
--- PASS: TestParseToken (0.00s)
PASS

Process finished with the exit code 0

在这里插入图片描述

四、总结

JWT(JSON Web Token)是一种用于身份验证的开放标准,它通过在用户和服务器之间传递被加密的 JSON 对象来安全地传输信息。JWT 由三个部分组成:头部、载荷和签名。其中,头部包含加密算法和 token 类型等信息,载荷包含存储在token中的用户信息,签名用于验证token的真实性和完整性。

使用 JWT 时需要注意以下几点:

  • 避免在 token中存储敏感信息,比如密码、银行卡号等。
  • 需要使用安全的算法来签名和加密 token,比如使用 HMACRSA 加密。
  • token中包含的用户信息不应该过多,只保留必要的信息即可。
  • 在使用 JWT 进行身份验证时,需要防止令牌被盗用。可以采用一些技术手段,比如限制令牌的有效期、限制令牌的使用次数等。
  • 在生成 token时需要注意加盐加密,以提高安全性。
  • 在解析 token时需要对 token进行校验,以保证 token的真实性和完整性。
  • 最后,需要注意遵循安全的开发实践,保障应用的安全性。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 Python JWT(JSON Web Token)的验证过程可以使用 `jwt` 库来实现。`jwt` 库提供了一些函数和方法来进行 JWT 的验证,其最常用的是 `jwt.decode()` 和 `jwt.verify_jwt()` 函数。 `jwt.decode()` 函数用于解码和验证 JWT,并返回 JWT 的 payload 数据。它接受三个参数:JWT 字符串、密钥和算法。 `jwt.verify_jwt()` 函数可以进行更加详细的 JWT 验证,包括验证签名、过期时间、生效时间等。它接受两个参数:JWT 字符串和密钥。 下面是一个简单的示例代码,展示了如何使用 `jwt.verify_jwt()` 函数进行 JWT 的验证: ```python import jwt def verify_jwt(jwt_token, secret_key): try: # 验证 JWT decoded_token = jwt.verify_jwt(jwt_token, secret_key) print("JWT 验证成功!") print("Payload 数据:", decoded_token) except jwt.JWTExpired: print("JWT 已过期!") except jwt.JWTInvalidSignature: print("JWT 签名无效!") except jwt.JWTInvalid: print("JWT 无效!") # 要验证的 JWT jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" # 密钥 secret_key = "my_secret_key" verify_jwt(jwt_token, secret_key) ``` 在上面的示例,首先定义了一个 `verify_jwt()` 函数,它接受 JWT 字符串和密钥作为参数。然后,在函数内部使用 `jwt.verify_jwt()` 函数对 JWT 进行验证。如果验证成功,将打印出 JWT 验证成功的信息以及解码后的 payload 数据;如果 JWT 过期、签名无效或无效,将会捕获相应的异常并打印出错误信息。 需要注意的是,JWT 的验证过程需要使用正确的密钥和算法,以确保安全性和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值