package middleware import ( "errors" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "fileshare/internal/response" ) // jwtClaims 自定义声明:对齐参考实现(payload 含 is_admin 与 exp)。 type jwtClaims struct { IsAdmin bool `json:"is_admin"` jwt.RegisteredClaims } // 签发/校验相关错误。 var ( ErrTokenExpired = errors.New("token已过期") ErrTokenInvalid = errors.New("无效的签名") ErrNotAdmin = errors.New("未授权或授权校验失败") ) // SignAdminToken 用 HS256 签发管理员 JWT。 // secret 为数据库 settings 中的 jwt_secret;expires 为会话有效期。 func SignAdminToken(secret string, expires time.Duration) (string, time.Time, error) { if strings.TrimSpace(secret) == "" { return "", time.Time{}, errors.New("JWT签名密钥未初始化") } expiresAt := time.Now().Add(expires) claims := jwtClaims{ IsAdmin: true, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expiresAt), IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: "fileshare", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString([]byte(secret)) return signed, expiresAt, err } // VerifyAdminToken 校验管理员 JWT:签名、过期时间与 is_admin 声明。 func VerifyAdminToken(secret, token string) (*jwtClaims, error) { if strings.TrimSpace(secret) == "" { return nil, errors.New("JWT签名密钥未初始化") } parsed, err := jwt.ParseWithClaims(token, &jwtClaims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrTokenInvalid } return []byte(secret), nil }, jwt.WithValidMethods([]string{"HS256"})) if err != nil { if errors.Is(err, jwt.ErrTokenExpired) { return nil, ErrTokenExpired } return nil, ErrTokenInvalid } claims, ok := parsed.Claims.(*jwtClaims) if !ok || !parsed.Valid { return nil, ErrTokenInvalid } if !claims.IsAdmin { return nil, ErrNotAdmin } return claims, nil } // SecretProvider 动态提供当前 jwt_secret(settings KV 运行时可变)。 type SecretProvider func() string // AdminAuth 管理员鉴权中间件:校验 Authorization: Bearer 。 // 成功后把声明写入 gin 上下文(ctxClaims)。 func AdminAuth(secret SecretProvider) gin.HandlerFunc { return func(c *gin.Context) { header := c.GetHeader("Authorization") if !strings.HasPrefix(header, "Bearer ") { response.Fail(c, 401, "未授权或授权校验失败") return } token := strings.TrimSpace(strings.TrimPrefix(header, "Bearer ")) if token == "" { response.Fail(c, 401, "未授权或授权校验失败") return } claims, err := VerifyAdminToken(secret(), token) if err != nil { response.Fail(c, 401, err.Error()) return } c.Set("claims", claims) c.Next() } }