• Gin + Ant Design Pro JWT认证


    一:介绍

    • JWT现在比较流行的认证方式,微服务中使用特别常见。
    • JWT标准格式

    HTTP请求添加名为Authorization的header,形式如下: (token前面Bearer 是标准前缀字符串
    Authorization: Bearer

    • Go JWT 结构体Claims,可以理解为需要保存的信息,加密后会存储在token中,解密后会从token自动解析出来

    配合ant design pro前端实现需要解决以下问题:

    • JWT TOKEN需要存储哪些字段以及定义,如何创建和解析
    • 前端发送请求时如何自动添加JWT认证信息
    • JWT TOKEN过期后,前后端如何配合自动刷新

    二:Gin JWT 后台

    使用库github.com/golang-jwt/jwt/v5

    1. Claims 定义

    jwt.RegisteredClaimsgithub.com/golang-jwt/jwt/v5预设的标准claims,后面用到其ExpiresAt过期时间字段,UserClaims 为自定义存储的字段,最终加密到token中的是jwtClaims结构

    type UserClaims struct {
    // 可以添加字段存储其他信息
    	UserID   uint   `json:"userId"`
    	RoleID   uint   `json:"roleId"`
    	UserName string `json:"username"`
    }
    
    type jwtClaims struct {
    	UserClaims
    	jwt.RegisteredClaims
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 创建和解析Token

    expireHoursrefreshHours分别是设置过期时间,以及距离过期时间多久返回新token,后续在gin中间件中判断,方式是在Header中返回x-refresh-tokennewToken

    type JWT struct {
    	secret       []byte
    	expireHours  time.Time
    	refreshHours time.Duration
    }
    
    // j := NewJWT("密钥", 2, 1)
    // token过期时间是2小时, 距离过期时间还有1小时时在`Header`中返回`x-refresh-token`:`newToken`
    func NewJWT(secret string, expireHours int, refreshHours int) *JWT {
    	return &JWT{
    		secret:       []byte(secret),
    		expireHours:  time.Now().Add(time.Duration(expireHours) * time.Hour),
    		refreshHours: time.Hour * time.Duration(refreshHours),
    	}
    }
    
    func (j *JWT) CreateToken(u UserClaims) (string, error) {
    	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims{
    		UserClaims: u,
    		RegisteredClaims: jwt.RegisteredClaims{
    			ExpiresAt: jwt.NewNumericDate(j.expireHours),
    			IssuedAt:  jwt.NewNumericDate(time.Now()),
    		},
    	})
    	return token.SignedString(j.secret)
    }
    
    func (j *JWT) ParseToken(tokenString string) (*jwtClaims, error) {
    	token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
    		return j.secret, nil
    	})
    	if err != nil {
    		return nil, err
    	}
    
    	if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid {
    		return claims, nil
    	} else {
    		return nil, errors.New("Token无效")
    	}
    }
    
    
    • 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

    3. Gin中间件编写

    type BaseResponse struct {
    	Success      bool   `json:"success"`
    	Data         any    `json:"data"`
    	Message      string `json:"message"`
    	ErrorMessage string `json:"errorMessage"`
    }
    
    func OkWithData(c *gin.Context, data any) {
    	c.JSON(200, BaseResponse{Success: true, Data: data})
    }
    
    func OkWithMsg(c *gin.Context, msg string) {
    	c.JSON(200, BaseResponse{Success: true, Message: msg})
    }
    
    func Fail(c *gin.Context, errMsg string) {
    	c.JSON(200, BaseResponse{Success: false, ErrorMessage: errMsg})
    }
    // 使用如下:
    // // token过期时间是2小时, 距离过期时间还有1小时时在`Header`中返回`x-refresh-token`:`newToken`
    // j := NewJWT("密钥", 2, 1)
    // r.USE(JWTAuth(j))
    func JWTAuth(j *JWT) gin.HandlerFunc {
    	return func(c *gin.Context) {
    		token := c.Request.Header.Get("Authorization")
    		token = strings.TrimPrefix(token, "Bearer ")
    		claims, err := j.ParseToken(token)
    		if err != nil {
    			response.Fail(c, "Token错误: "+err.Error())
    			c.Abort()
    			return
    		}
    		if time.Since(claims.ExpiresAt.Local()) < j.refreshHours {
    			newToken, err := j.CreateToken(claims.UserClaims)
    			if err != nil {
    				response.Fail(c, "Token刷新错误: "+err.Error())
    				c.Abort()
    				return
    			}
    			c.Header("x-refresh-token", newToken)
    		}
    		c.Set("claims", claims)
    		c.Next()
    	}
    
    }
    
    • 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

    4. 辅助函数

    gin中直接调用该函数可以方便的获取到存储的机构体信息

    func GetUserToken(c *gin.Context) *UserClaims {
    	claims, _ := c.Get("claims")
    	if claims == nil {
    		return &UserClaims{}
    	}
    	return &claims.(*jwtClaims).UserClaims
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    三:Ant Design Pro JWT认证

    处理登录请求后返回jwt token使用localStorage.setItem('jwt', token)存储起来
    app.tsx的文件request定义中添加拦截器,实现方法:

    • 请求前,在header中添加头·Authorization·: 'Bearer ’ + localStorage.getItem(‘jwt’) || ‘’
    • 请求后,检测header中是否存在x-refresh-token,存在的话更新token,目的是防止token过期自动刷新
      实现如下:
    function setToken(token: string) {
      localStorage.setItem('jwt', token);
    }
    
    function getToken(): string {
      return localStorage.getItem('jwt') || '';
    }
    
    export const request = {
      ...errorConfig,
      // 请求前拦截器
      requestInterceptors: [
        (url: string, options: RequestConfig) => {
          const authHeader = { Authorization: 'Bearer ' + getToken() };
          return {
            url: `${url}`,
            options: { ...options, interceptors: true, headers: authHeader },
          };
        },
      ],
      // 请求后拦截器
      responseInterceptors: [
        (response: Response, options: RequestConfig) => {
          if (response.headers.has('x-refresh-token')) {
            setToken(response.headers.get('x-refresh-token') || '');
          }
          return response;
        },
      ],
    };
    
    
    • 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

    四:Gin中间件和使用示范

    jwt_middleware.go

    package middleware
    
    import (
    	"errors"
    	"strings"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/golang-jwt/jwt/v5"
    )
    
    // 可以添加字段存储其他信息
    type UserClaims struct {
    	UserID   uint   `json:"userId"`
    	RoleID   uint   `json:"roleId"`
    	UserName string `json:"username"`
    }
    
    type jwtClaims struct {
    	UserClaims
    	jwt.RegisteredClaims
    }
    
    type JWT struct {
    	secret       []byte
    	expireHours  time.Time
    	refreshHours time.Duration
    }
    
    // j := NewJWT("密钥", 2, 1)
    // token过期时间是2小时, 距离过期时间还有1小时时在`Header`中返回`x-refresh-token`:`newToken`
    func NewJWT(secret string, expireHours int, refreshHours int) *JWT {
    	return &JWT{
    		secret:       []byte(secret),
    		expireHours:  time.Now().Add(time.Duration(expireHours) * time.Hour),
    		refreshHours: time.Hour * time.Duration(refreshHours),
    	}
    }
    
    func (j *JWT) CreateToken(u UserClaims) (string, error) {
    	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims{
    		UserClaims: u,
    		RegisteredClaims: jwt.RegisteredClaims{
    			ExpiresAt: jwt.NewNumericDate(j.expireHours),
    			IssuedAt:  jwt.NewNumericDate(time.Now()),
    		},
    	})
    	return token.SignedString(j.secret)
    }
    
    func (j *JWT) ParseToken(tokenString string) (*jwtClaims, error) {
    	token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
    		return j.secret, nil
    	})
    	if err != nil {
    		return nil, err
    	}
    
    	if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid {
    		return claims, nil
    	} else {
    		return nil, errors.New("Token无效")
    	}
    }
    
    // 使用如下:
    // // token过期时间是2小时, 距离过期时间还有1小时时在`Header`中返回`x-refresh-token`:`newToken`
    // j := NewJWT("密钥", 2, 1)
    // r.USE(JWTAuth(j))
    func JWTAuth(j *JWT) gin.HandlerFunc {
    	return func(c *gin.Context) {
    		token := c.Request.Header.Get("Authorization")
    		token = strings.TrimPrefix(token, "Bearer ")
    		claims, err := j.ParseToken(token)
    		if err != nil {
    			response.Fail(c, "Token错误: "+err.Error())
    			c.Abort()
    			return
    		}
    		if time.Since(claims.ExpiresAt.Local()) < j.refreshHours {
    			newToken, err := j.CreateToken(claims.UserClaims)
    			if err != nil {
    				response.Fail(c, "Token刷新错误: "+err.Error())
    				c.Abort()
    				return
    			}
    			c.Header("x-refresh-token", newToken)
    		}
    		c.Set("claims", claims)
    		c.Next()
    	}
    
    }
    
    func GetUserToken(c *gin.Context) *UserClaims {
    	claims, _ := c.Get("claims")
    	if claims == nil {
    		return &UserClaims{}
    	}
    	return &claims.(*jwtClaims).UserClaims
    }
    
    type BaseResponse struct {
    	Success      bool   `json:"success"`
    	Data         any    `json:"data"`
    	Message      string `json:"message"`
    	ErrorMessage string `json:"errorMessage"`
    }
    
    func OkWithData(c *gin.Context, data any) {
    	c.JSON(200, BaseResponse{Success: true, Data: data})
    }
    
    func OkWithMsg(c *gin.Context, msg string) {
    	c.JSON(200, BaseResponse{Success: true, Message: msg})
    }
    
    func Fail(c *gin.Context, errMsg string) {
    	c.JSON(200, BaseResponse{Success: false, ErrorMessage: errMsg})
    }
    
    
    
    • 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
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    main.go

    package middleware
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	j := NewJWT("密钥", 2, 1)
    	r := gin.Default()
    
    	r.POST("/login", func(ctx *gin.Context) {
    		// 处理登录逻辑返回token, 前端需讲token存储到localstorage
    		token, err := j.CreateToken(UserClaims{UserID: 1, RoleID: 1, UserName: "Leo"})
    		if err != nil {
    			response.Fail(ctx, err.Error())
    			return
    		}
    		response.OkWithData(ctx, gin.H{"token": token})
    	})
    
    	auth := r.Group("/api")
    	auth.Use(JWTAuth(j))
    	auth.GET("/test", func(ctx *gin.Context) {
        u := GetUserToken(ctx)
    		response.OkWithMsg(ctx, fmt.Sprintf("用户ID:%d角色ID:%d用户名:%s", u.UserID, u.RoleID, u.UserName))
    	})
    }
    
    
    • 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
  • 相关阅读:
    【机器学习Q&A】准确率、精确率、召回率、ROC和AUC
    WuThreat身份安全云-TVD每日漏洞情报-2022-12-06
    Unity之NetCode多人网络游戏联机对战教程(3)--NetworkObject组件讲解
    预约按摩app软件开发定制足浴SPA上们服务小程序
    重温C语言十五---C语言文件管理操作
    【PyQt学习篇 · ③】:QObject - 神奇的对象管理工具
    Java练习题
    普通人网上赚钱,抖音机会仍旧会非常大!
    JavaEE进阶 - SpringBoot 统⼀功能处理 - 细节狂魔
    python经典百题之打印素数
  • 原文地址:https://blog.csdn.net/LeoForBest/article/details/133869848