From 525ad7b7ff1cee0706f9fb4b69bd3019b61967aa Mon Sep 17 00:00:00 2001 From: mayiming <1627832236@qq.com> Date: Sun, 10 Aug 2025 02:10:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0jwt=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utill/jwtUt.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 utill/jwtUt.go diff --git a/utill/jwtUt.go b/utill/jwtUt.go new file mode 100644 index 0000000..0197b19 --- /dev/null +++ b/utill/jwtUt.go @@ -0,0 +1,106 @@ +package utill + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "time" + "toutoukan/config" + "toutoukan/init/redisInit" + "toutoukan/model/usermodel" +) + +// 生成JWT并存储到Redis +func GenerateJWTAndStore(openid string) (string, error) { + // 1. 生成JWT + expirationTime := time.Now().Add(2 * time.Hour) + claims := &usermodel.JWTClaims{ + OpenID: openid, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + IssuedAt: jwt.NewNumericDate(time.Now()), + Issuer: "toutoukan", + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(config.Conf.Jwtsecret)) + if err != nil { + return "", err + } + + // 2. 存储到Redis(键:jwt:{token},值:openid,过期时间与JWT一致) + redisKey := fmt.Sprintf("jwt:%s", tokenString) + err = redisInit.RedisClient.Set( + redisInit.Ctx, + redisKey, + openid, + 2*time.Hour, + ).Err() + + return tokenString, err +} + +// 验证JWT并检查Redis +func ValidateJWTWithRedis(tokenString string) (*usermodel.JWTClaims, error) { + // 1. 先验证JWT签名和过期时间 + token, err := jwt.ParseWithClaims( + tokenString, + &usermodel.JWTClaims{}, + func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return []byte(config.Conf.Jwtsecret), nil + }, + ) + if err != nil { + return nil, err + } + + // 2. 验证Redis中是否存在(防止已登出的令牌) + redisKey := fmt.Sprintf("jwt:%s", tokenString) + exists, err := redisInit.RedisClient.Exists(redisInit.Ctx, redisKey).Result() + if err != nil { + return nil, fmt.Errorf("redis查询失败: %w", err) + } + if exists == 0 { + return nil, fmt.Errorf("令牌已失效") + } + + // 3. 验证声明有效性 + if claims, ok := token.Claims.(*usermodel.JWTClaims); ok && token.Valid { + return claims, nil + } + return nil, jwt.ErrSignatureInvalid +} + +// 辅助函数:从请求头提取令牌 +func ExtractTokenFromHeader(c *gin.Context) string { + authHeader := c.GetHeader("Authorization") + var tokenString string + fmt.Sscanf(authHeader, "Bearer %s", &tokenString) + return tokenString +} + +// JWT+Redis验证中间件 +func JWTAuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + tokenString := ExtractTokenFromHeader(c) + if tokenString == "" { + c.JSON(401, gin.H{"error": "请先登录", "code": "10033"}) + c.Abort() + return + } + + claims, err := ValidateJWTWithRedis(tokenString) + if err != nil { + c.JSON(401, gin.H{"error": "令牌无效或已过期", "code": "10034"}) + c.Abort() + return + } + + c.Set("openid", claims.OpenID) + c.Next() + } +}