package jwt import ( "errors" "strings" "time" "github.com/gin-gonic/gin" jwtpkg "github.com/golang-jwt/jwt/v5" "github.com/runningwater/gohub/pkg/app" "github.com/runningwater/gohub/pkg/config" "github.com/runningwater/gohub/pkg/logger" ) var ( ErrTokenExpired error = errors.New("令牌已过期") ErrTokenNotValidYet error = errors.New("令牌尚未生效") ErrTokenMalformed error = errors.New("令牌格式错误") ErrTokenInvalid error = errors.New("令牌无效") ErrTokenExpiredMaxRefresh error = errors.New("令牌已过期,无法刷新") ErrHeaderEmpty error = errors.New("需要认证才能访问!") ErrHeaderMalformed error = errors.New("请求头格式错误") ) // JWT 结构体,包含签名密钥和刷新 Token 的最大过期时间 type JWT struct { // 签名密钥,用于生成和验证 JWT 签名,读取 app.key SigningKey []byte // 刷新 Token 的最大过期时间 MaxRefresh time.Duration } // CustomClaims 自定义的 JWT 载荷结构体,包含用户 ID、用户名 和过期时间 type CustomClaims struct { // 用户 ID UserID string `json:"user_id"` // 用户名 UserName string `json:"user_name"` // 过期时间 ExpireTime int64 `json:"expire_time"` // 标准的 JWT 载荷字段 // Audience aud : 受众 // Issuer iss : 签发者 // Not Before nbf : 生效时间 // Issued At iat : 签发时间 // JWT ID jti : JWT ID 编号 // Subject sub : 主题 // Expires At exp : 过期时间 // 标准的 JWT 载荷字段 jwtpkg.RegisteredClaims } func NewJWT() *JWT { return &JWT{ SigningKey: []byte(config.GetString("app.key")), MaxRefresh: time.Duration(config.GetInt64("jwt.max_refresh_time")) * time.Minute, } } // ParseToken 解析 Token func (j *JWT) ParseToken(c *gin.Context) (*CustomClaims, error) { // 1. 获取请求头中的 Token tokenString, err := j.getTokenFromHeader(c) if err != nil { return nil, err } // 2. 调用 jwt 库解析用户传入的 token token, err := j.parseTokenString(tokenString) // 3. 解析出错, 未报错证明是合法的 token if err != nil { return nil, err // validationErr, ok := err.(*jwtpkg.ValidationError) // if ok { // switch validationErr.Errors { // case jwtpkg.ValidationErrorMalformed: // return nil, ErrTokenMalformed // case jwtpkg.ValidationErrorExpired: // return nil, ErrTokenExpired // default: // return nil, ErrTokenInvalid // } // } // } // return nil, ErrTokenInvalid } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil } return nil, ErrTokenInvalid } // IssueToken 签发 Token func (j *JWT) IssueToken(userID, userName string) string { // 1. 构造用户 claims 信息(负荷) expireTime := j.expireAtTime() claims := CustomClaims{ userID, userName, expireTime.Unix(), jwtpkg.RegisteredClaims{ NotBefore: jwtpkg.NewNumericDate(app.TimenowInTimezone()), // 生效时间 IssuedAt: jwtpkg.NewNumericDate(app.TimenowInTimezone()), // 签发时间 ExpiresAt: jwtpkg.NewNumericDate(expireTime), // 过期时间 Issuer: config.GetString("app.name"), // 签发者 Subject: userID, // 主题 Audience: []string{userName}, // 受众 }, } // 2. 根据 claims 生成 Token token, err := j.generateToken(claims) if err != nil { logger.LogIf(err) return "" } return token } // RefreshToken 刷新 Token func (j *JWT) RefreshToken(c *gin.Context) (string, error) { // 1. 从 Header 里获取 token tokenString, err := j.getTokenFromHeader(c) if err != nil { return "", err } // 2. 调用 jwt 库解析用户传入的 token token, err := j.parseTokenString(tokenString) // 3. 解析出错, 未报错证明是合法的 token if err != nil { // validationErr, ok := err.(*jwtpkg.ValidationError) // // 满足刷新条件:只是单一的报错 ValidationErrorExpired // if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired { // return "", err // } if errors.Is(err, jwtpkg.ErrTokenExpired) { // 过期了,返回错误 return "", err } } // 4. 解析 claims claims := token.Claims.(*CustomClaims) // 5. 判断是否过了最大允许刷新的时间 x := app.TimenowInTimezone().Add(-j.MaxRefresh).Unix() if claims.IssuedAt.Unix() > x { // 修改过期时间 claims.ExpiresAt = jwtpkg.NewNumericDate(j.expireAtTime()) return j.generateToken(*claims) } // 6. 超过最大允许刷新的时间,返回错误 return "", ErrTokenExpiredMaxRefresh } // generateToken 生成 Token, 内部使用,外部请调用 IssueToken func (j *JWT) generateToken(claims CustomClaims) (string, error) { // 创建一个新的 Token 对象,使用 HMAC SHA256 签名方法 token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims) return token.SignedString(j.SigningKey) } // expireAtTime 计算过期时间 func (j *JWT) expireAtTime() time.Time { timeNow := app.TimenowInTimezone() var expireTime int64 if config.GetBool("app.debug") { expireTime = config.GetInt64("jwt.debug_expire_time") } else { expireTime = config.GetInt64("jwt.expire_time") } expire := time.Duration(expireTime) * time.Minute return timeNow.Add(expire) } // getTokenFromHeader 获取请求头中的 Token // Authorization: Bearer Token // 从请求头中获取 Token,格式为 "Bearer Token" func (j *JWT) getTokenFromHeader(c *gin.Context) (string, error) { // 获取请求头中的 Token authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { return "", ErrHeaderEmpty } // 检查请求头格式是否正确 // Bearer Token // Bearer 后面有一个空格 parts := strings.SplitN(authHeader, " ", 2) if len(parts) < 2 || parts[0] != "Bearer" { return "", ErrHeaderMalformed } // 返回 Token 字符串 return parts[1], nil } // parseTokenString 解析 Token 字符串 func (j *JWT) parseTokenString(tokenString string) (*jwtpkg.Token, error) { return jwtpkg.ParseWithClaims( tokenString, &CustomClaims{}, func(token *jwtpkg.Token) (any, error) { return j.SigningKey, nil }, ) }