在gin中使用jwt鉴权


jwt是什么

jwt全称 JSON Web Token,是一种跨域认证的解决方案。

jwt由三部分组成,中间使用.进行分割,格式如下:

header.payload.signature

  • header: 是一个JSON对象,包含如下内容
    {
      "typ": "JWT",//默认为JWT
      "alg": "HS256"//支持多种加密算法
    }
  • payload: 也是一个JSON对象,用来存放实际所需的数据。包括如下JWT标准提供的7种可选数据和自定义数据
字段 描述
iss 签发者
sub 主题,用于鉴别一个用户
exp 过期时间
aud 受众
iat 签发时间
nbf 生效时间
jti jwt ID
  • signature: 签名,将header和payload进行Base64URL编码后,再用指定密钥和header中的加密算法进行加密得到

jwt的特点

相较于传统的Cookie和Session的认证方式,jwt不需要像Session那样在服务器上存储信息,也没有Cookie的大小限制,更加灵活,也更适用于分布式系统。

在gin中使用jwt

使用jwt-go

go get -u github.com/golang-jwt/jwt/v4
import "github.com/golang-jwt/jwt/v4"

token生成函数和解析函数

在样例中,我们在payload中存储用户id,读者可根据自己需要添加任意信息。

package util

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

// MyClaims 自定义的payload
type MyClaims struct {
	jwt.RegisteredClaims        // 内置的payload字段
	UserID               string `json:"user_id"` // 自定义的payload字段
}

type JWT struct {
}

var key = []byte("qwouidfoqiwr23fioqw") // 用于签名的密钥

func (j *JWT) GenerateToken(msg string, ttl int64) string {
	now := time.Now()
	// 创建一个新的token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, &MyClaims{
		UserID: msg,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(24*ttl) * time.Hour)), // 过期时间,ttl天后过期
			IssuedAt:  jwt.NewNumericDate(now),                                        // 签发时间
			NotBefore: jwt.NewNumericDate(now),                                        // 生效时间
			Issuer:    "tiktok-lite",                                                  // 签发人
		},
	})
	tokenString, _ := token.SignedString(key) // 签名, 返回token字符串
	return tokenString
}

func (j *JWT) ParseToken(tokenString string) (string, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
		return key, nil
	})

	if err != nil {
		log.Println(err)
		return "", errors.New("invalid token")
	}
	
	claims, ok := token.Claims.(*MyClaims)
	if !ok || !token.Valid {
		log.Println("invalid token")
		return "", errors.New("invalid token")
	}
	return claims.UserID, nil
}

gin中间件

构造一个jwt鉴权中间件,在需要用户鉴权的路由中使用。

一般token信息会放在请求的header的Authorization字段中,并以Bearer +token字符串的方式发送。本文采用URL参数的方式发送token,例如:http://domain.com/user/info?user_id=xxxxx&token=xxxxxxx

func JWTAuth() func(c *gin.Context) {
	return func(c *gin.Context) {
		userId := c.Query("user_id")
		token := c.Query("token")
		if userId == "" || token == "" {
			c.JSON(200, &controller.UserInfoRes{
				Code: -1,
				Msg:  "缺失参数",
				User: nil,
			})
			c.Abort()
			return
		}
		jwt := util.JWT{} // 上文实现的JWT
		claims, err := jwt.ParseToken(token)
		if err != nil || claims != userId {
			c.JSON(200, &controller.UserInfoRes{
				Code: -1,
				Msg:  "token验证失败",
				User: nil,
			})
			c.Abort()
			return
		}
		c.Next()
	}
}

login时生成token

func Login(username string, password string) (*UserToken, error) {
	...
	jwt := util.JWT{}
	token := jwt.GenerateToken(strconv.Itoa(int(data[0].ID)), 1)
	return &UserToken{
		...
		Token: token,
	}, nil
}

使用中间件

user := r.Group("/user")
user.GET("/info", middleware.JWTAuth(), func(c *gin.Context) {
    userID := c.Query("user_id")
    userInfo := controller.GetUserInfo(userID)
    c.JSON(200, userInfo)
})

  目录