Gin + Ant Design Pro JWT认证

文章目录

  • 一:介绍
  • 二:Gin JWT 后台
    • 1. `Claims `定义
    • 2. 创建和解析Token
    • 3. Gin中间件编写
    • 4. 辅助函数
  • 三:Ant Design Pro JWT认证
  • 四:Gin中间件和使用示范

一:介绍

  • 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
}

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无效")
	}
}

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()
	}

}

4. 辅助函数

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

func GetUserToken(c *gin.Context) *UserClaims {
	claims, _ := c.Get("claims")
	if claims == nil {
		return &UserClaims{}
	}
	return &claims.(*jwtClaims).UserClaims
}

三: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;
    },
  ],
};

四: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})
}


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))
	})
}

你可能感兴趣的:(gin,react,anti-design-vue)