runningwater преди 5 месеца
родител
ревизия
f8184a4cdf
променени са 2 файла, в които са добавени 199 реда и са изтрити 197 реда
  1. 30 29
      app/cmd/serve.go
  2. 169 168
      pkg/jwt/jwt.go

+ 30 - 29
app/cmd/serve.go

@@ -1,40 +1,41 @@
 package cmd
 
 import (
-	"github.com/gin-gonic/gin"
-	"github.com/runningwater/gohub/bootstrap"
-	"github.com/runningwater/gohub/pkg/config"
-	"github.com/runningwater/gohub/pkg/console"
-	"github.com/runningwater/gohub/pkg/logger"
-	"github.com/spf13/cobra"
+    "github.com/gin-gonic/gin"
+    "github.com/spf13/cobra"
+
+    "github.com/runningwater/gohub/bootstrap"
+    "github.com/runningwater/gohub/pkg/config"
+    "github.com/runningwater/gohub/pkg/console"
+    "github.com/runningwater/gohub/pkg/logger"
 )
 
 var CmdServe = &cobra.Command{
-	Use:   "serve",
-	Short: "Start web server",
-	Long:  "Start the server - Gin server",
-	Run:   runWeb,
-	Args:  cobra.NoArgs,
+    Use:   "serve",
+    Short: "Start web server",
+    Long:  "Start the server - Gin server",
+    Run:   runWeb,
+    Args:  cobra.NoArgs,
 }
 
 func runWeb(cmd *cobra.Command, args []string) {
-	// 设置 gin 的运行模式, 支持 debug, release, test
-	// release 模式会屏蔽调试信息,官方建议生产环境中使用
-	// 非 release 模式 gin 终端打印太多信息,干扰到我们程序中的 Log
-	// 故此设置为 release 模式
-	gin.SetMode(gin.ReleaseMode)
-
-	// gin 框架初始化
-	router := gin.New()
-
-	// 初始化路由绑定
-	bootstrap.SetupRoute(router)
-
-	// 运行服务
-	err := router.Run(":" + config.Get("app.port"))
-	if err != nil {
-		logger.ErrorString("CMD", "serve", err.Error())
-		console.Exit("Unable to start server, error:" + err.Error())
-	}
+    // 设置 gin 的运行模式, 支持 debug, release, test
+    // release 模式会屏蔽调试信息,官方建议生产环境中使用
+    // 非 release 模式 gin 终端打印太多信息,干扰到我们程序中的 Log
+    // 故此设置为 release 模式
+    gin.SetMode(gin.ReleaseMode)
+
+    // gin 框架初始化
+    router := gin.New()
+
+    // 初始化路由绑定
+    bootstrap.SetupRoute(router)
+
+    // 运行服务
+    err := router.Run(":" + config.Get("app.port"))
+    if err != nil {
+        logger.ErrorString("CMD", "serve", err.Error())
+        console.Exit("Unable to start server, error:" + err.Error())
+    }
 
 }

+ 169 - 168
pkg/jwt/jwt.go

@@ -1,221 +1,222 @@
 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"
+    "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("请求头格式错误")
+    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
+    // 签名密钥,用于生成和验证 JWT 签名,读取 app.key
+    SigningKey []byte
 
-	// 刷新 Token 的最大过期时间
-	MaxRefresh time.Duration
+    // 刷新 Token 的最大过期时间
+    MaxRefresh time.Duration
 }
 
-// JWTCustomClaims 自定义的 JWT 载荷结构体,包含用户 ID、用户名 和过期时间
-type JWTCustomClaims 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
+// 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,
-	}
+    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) (*JWTCustomClaims, 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.(*JWTCustomClaims); ok && token.Valid {
-		return claims, nil
-	}
-	return nil, ErrTokenInvalid
+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 := JWTCustomClaims{
-		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
+    // 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.(*JWTCustomClaims)
-
-	// 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
+    // 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 JWTCustomClaims) (string, error) {
+func (j *JWT) generateToken(claims CustomClaims) (string, error) {
 
-	// 创建一个新的 Token 对象,使用 HMAC SHA256 签名方法
-	token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
+    // 创建一个新的 Token 对象,使用 HMAC SHA256 签名方法
+    token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
 
-	return token.SignedString(j.SigningKey)
+    return token.SignedString(j.SigningKey)
 }
 
 // expireAtTime 计算过期时间
 func (j *JWT) expireAtTime() time.Time {
-	timeNow := app.TimenowInTimezone()
+    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")
-	}
+    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
+    expire := time.Duration(expireTime) * time.Minute
 
-	return timeNow.Add(expire)
+    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
+    // 获取请求头中的 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,
-		&JWTCustomClaims{},
-		func(token *jwtpkg.Token) (any, error) {
-			return j.SigningKey, nil
-		},
-	)
+    return jwtpkg.ParseWithClaims(
+        tokenString,
+        &CustomClaims{},
+        func(token *jwtpkg.Token) (any, error) {
+            return j.SigningKey, nil
+        },
+    )
 }