Gin框架下的JWT

Gin框架下的JWT

什么是JWT

我们都知道,HTTP协议是无状态的,click here,那么服务端怎么知道用户状态的呢(比如是否登录呢),这里就需要用到中间件来进行用户认证。

中间件认证有这么几种方式

  • session

  • token

    token和session最大的区别就是token是存储在客户端的:
    我们都知道,session是存储在服务端的,每次请求到来的时候,通过cookie携带的sessionID与服务器内部存储的在线session进行比对,如果有该sessionID,就说明该用户已登录,但是这样就暴露出session最大的确定,服务端压力过大,同时如果是服务器是一个集群,在负载均衡后很可能处理该请求的服务器并不是存储该请求session的服务器。所以为了解决这些问题,token便产生了。

token和session类似,单不存储在服务端,当用户请求连接时,服务端分发一个token表示该用户的“令牌”,每次用户发器请求都携带这个令牌在请求头中(为保证安全性大部分存储在请求头中),和session一样,客户端只转发,不处理token,客户端收到带有token打请求后解析token,解析出的信息就可以唯一标识某个用户(例如预设的UID)。

JWT,读作:jot ,表示:JSON Web Tokens

JWT 标准的 Token 由三个部分组成:header.payload.signature

header(头部)
payload(数据)
signature(签名)

中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

每个 JWT token 里面都有一个 header,也就是头部数据。里面包含了使用的算法,这个 JWT 是不是带签名的或者加密的。主要就是说明一下怎么处理这个 JWT token 。

唯一在头部里面要包含的是 alg 这个属性,如果是加密的 JWT,这个属性的值就是使用的签名或者解密用的算法。如果是未加密的 JWT,这个属性的值要设置成 none。

Payload

Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。
例如:UID , timeLogin , Ifadmin等等

Signature

JWT 的最后一部分是 Signature ,这部分内容有三个部分——

  • 第一部分 :Base64 编码的header。
  • 第二部分:Base64 编码的payload。
  • 第三部分:再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端,不得泄露。

处理过程可以理解为

const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); 
HMACSHA256(encodedString, 'secret');

基于gin框架的JWT

废话不多说,直接上代码,原理上面讲过,详情请见注释!

package main

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v4"
)

var mySecret = []byte("haomiao000") // 直接将密钥作为字符串存储(这里只是个例子,通常会非常复杂保证安全性)

// 定义自定义声明结构体并内嵌 jwt.RegisteredClaims
type MyClaims struct {
	UserID int64 `json:"user_id"`
	jwt.RegisteredClaims
}

// 生成JWT
func GenToken(userID int64) (string, error) {
	// 创建自定义声明
	claims := MyClaims{
		UserID: userID,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), // 过期时间设置为1小时
			Issuer:    "jwt",                                             // 签发人
		},
	}
	// 使用指定的签名方法创建签名对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return token.SignedString(mySecret)
}

// 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
	var claims MyClaims
	// 解析token
	token, err := jwt.ParseWithClaims(tokenString, &claims, keyFunc)
	if err != nil {
		return nil, fmt.Errorf("parsing token: %w", err)
	}
	// 校验token
	if !token.Valid {
		return nil, errors.New("invalid token")
	}
	return &claims, nil
}

// keyFunc 定义获取密钥的函数
func keyFunc(_ *jwt.Token) (interface{}, error) {
	return mySecret, nil
}

func main() {
	// 初始化Gin引擎
	r := gin.Default()

	// 设置路由
	r.POST("/login", loginHandler)
	authGroup := r.Group("/api")
	authGroup.Use(authMiddleware())
	authGroup.GET("/user", getUserHandler)

	// 启动服务器
	r.Run(":8080")
}

// authMiddleware 定义JWT身份验证中间件
func authMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		tokenString := c.GetHeader("Authorization")
		if tokenString == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			c.Abort()
			return
		}

		claims, err := ParseToken(tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			c.Abort()
			return
		}

		// 在上下文中设置用户ID
		c.Set("userID", claims.UserID)

		// 继续处理请求
		c.Next()
	}
}

// loginHandler 登录处理程序
func loginHandler(c *gin.Context) {
	// 这里可以编写身份验证逻辑
	// 例如从请求中获取用户名和密码,然后验证用户信息

	// 假设身份验证成功后,获取用户ID
	userID := int64(123)

	// 生成JWT
	token, err := GenToken(userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}

	// 返回JWT给客户端
	c.JSON(http.StatusOK, gin.H{"token": token})
}

// getUserHandler 示例处理程序,需要JWT验证
func getUserHandler(c *gin.Context) {
	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}

	// 在这里可以使用userID进行进一步的处理,例如从数据库中检索用户信息等

	c.JSON(http.StatusOK, gin.H{"userID": userID})
}
  • 请求拦截(请求附加token)

  • 登录生成token

  • 验证token

posted @ 2024-06-06 22:16  AtongM  阅读(89)  评论(0编辑  收藏  举报