107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
|
|
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()
|
|||
|
|
}
|
|||
|
|
}
|