package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/gouki/lunar-server/internal/config" "github.com/gouki/lunar-server/internal/service" ) // CORS 跨域中间件;可通过 CORS_ORIGINS 配置允许的源(逗号分隔),未配置时保持 * func CORS() gin.HandlerFunc { cfg := config.Load() origins := strings.TrimSpace(cfg.Server.CORSOrigins) allowAll := origins == "" allowed := map[string]bool{} if !allowAll { for _, o := range strings.Split(origins, ",") { o = strings.TrimSpace(o) if o != "" { allowed[o] = true } } } return func(c *gin.Context) { origin := c.GetHeader("Origin") if origin != "" { if allowAll { c.Header("Access-Control-Allow-Origin", "*") } else if allowed[origin] { c.Header("Access-Control-Allow-Origin", origin) c.Header("Vary", "Origin") } else { // 非白名单源:不输出 CORS 头,浏览器会拦截跨域请求 if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return } c.Next() return } c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Pay-Sign") } if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } // bearerToken 从 Authorization 头提取 Bearer token,格式错误时返回 ok=false 并已应答 func bearerToken(c *gin.Context) (string, bool) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "未授权", }) c.Abort() return "", false } parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "token格式错误", }) c.Abort() return "", false } return parts[1], true } // Auth JWT认证中间件 func Auth() gin.HandlerFunc { return func(c *gin.Context) { tokenString, ok := bearerToken(c) if !ok { return } cfg := config.Load() userService := service.NewUserService() userID, err := userService.ParseToken(tokenString, cfg.JWT.Secret) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "token无效", }) c.Abort() return } c.Set("userID", userID) c.Next() } } // AdminAuth 管理员认证中间件:要求携带 role=admin 的 JWT(Authorization 头或 HttpOnly Cookie) // 浏览器导航(GET 页面请求)未登录时渲染登录页;API 请求未登录时返回 401 JSON func AdminAuth() gin.HandlerFunc { return func(c *gin.Context) { tokenString := "" authHeader := c.GetHeader("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { tokenString = strings.TrimPrefix(authHeader, "Bearer ") } else { // 浏览器导航场景:登录时写入的 HttpOnly Cookie tokenString, _ = c.Cookie(service.AdminTokenCookie) } authed := false if tokenString != "" { cfg := config.Load() userService := service.NewUserService() authed = userService.IsAdminToken(tokenString, cfg.JWT.Secret) } if !authed { // 页面导航(GET 且非 /admin/api/):返回登录页 if c.Request.Method == http.MethodGet && !strings.HasPrefix(c.Request.URL.Path, "/admin/api/") { c.HTML(http.StatusOK, "index.html", gin.H{ "title": "管理员登录", "page": "login", "url": c.Request.URL.Path, }) c.Abort() return } c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "需要管理员权限", }) c.Abort() return } c.Next() } } // RateLimit 限流中间件 func RateLimit() gin.HandlerFunc { return func(c *gin.Context) { // TODO: 实现限流 c.Next() } }