From ac31cc059a891108d60d5df534a6b116beaaa3ef Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Wed, 12 Aug 2026 12:09:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=87=E6=BB=A4=E9=9D=9E=E6=B3=95=20C?= =?UTF-8?q?ORS=20Origin=EF=BC=8C=E9=81=BF=E5=85=8D=20Docker=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=20panic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gin-contrib/cors 不接受 tauri://,启动时过滤仅保留 http/https。 Co-authored-by: Cursor --- backend/config/config.go | 42 +++++++++++++++++++++++++++----------- backend/middleware/cors.go | 18 +++++++++++++++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/backend/config/config.go b/backend/config/config.go index ae20d5c..52fad4c 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -48,18 +48,22 @@ func Load() *Config { DBPassword: getEnv("DB_PASSWORD", ""), DBName: getEnv("DB_NAME", "db.sqlite3"), JWTSecret: getEnv("JWT_SECRET", ""), - CORSOrigins: []string{ - "http://localhost:5181", - "http://127.0.0.1:5181", - "http://localhost:3000", - "http://127.0.0.1:3000", - // OpsXShell Tauri / Vite dev origins - "http://localhost:1420", - "http://127.0.0.1:1420", - "http://tauri.localhost", - "https://tauri.localhost", - "tauri://localhost", - }, + CORSOrigins: defaultCORSOrigins(), + } + + if raw := strings.TrimSpace(os.Getenv("CORS_ORIGINS")); raw != "" { + parts := strings.Split(raw, ",") + origins := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { + continue + } + origins = append(origins, p) + } + if len(origins) > 0 { + cfg.CORSOrigins = origins + } } // 安全检查:生产环境拒绝空密钥,开发环境使用进程级随机密钥避免固定后门。 // Logan.Li @@ -74,6 +78,20 @@ func Load() *Config { return cfg } +func defaultCORSOrigins() []string { + return []string{ + "http://localhost:5181", + "http://127.0.0.1:5181", + "http://localhost:3000", + "http://127.0.0.1:3000", + // OpsXShell Tauri / Vite dev origins (gin-cors only accepts http/https) + "http://localhost:1420", + "http://127.0.0.1:1420", + "http://tauri.localhost", + "https://tauri.localhost", + } +} + // IsOriginAllowed 判断浏览器 Origin 是否在显式 CORS 白名单中。 // Logan.Li func (c *Config) IsOriginAllowed(origin string) bool { for _, allowed := range c.CORSOrigins { diff --git a/backend/middleware/cors.go b/backend/middleware/cors.go index 10183a8..4dc9dc2 100644 --- a/backend/middleware/cors.go +++ b/backend/middleware/cors.go @@ -2,6 +2,7 @@ package middleware import ( "backend/config" + "strings" "time" "github.com/gin-contrib/cors" @@ -10,7 +11,7 @@ import ( // CORSMiddleware CORS 中间件,使用显式白名单避免携带凭据时放行任意来源。 // Logan.Li func CORSMiddleware(cfg *config.Config) gin.HandlerFunc { - origins := cfg.CORSOrigins + origins := filterHTTPOrigins(cfg.CORSOrigins) if len(origins) == 0 { origins = []string{ "http://localhost:5181", @@ -29,3 +30,18 @@ func CORSMiddleware(cfg *config.Config) gin.HandlerFunc { MaxAge: 12 * time.Hour, }) } + +// filterHTTPOrigins keeps only origins gin-contrib/cors accepts (http/https or *). // Logan.Li +func filterHTTPOrigins(origins []string) []string { + out := make([]string, 0, len(origins)) + for _, origin := range origins { + o := strings.TrimSpace(origin) + if o == "" { + continue + } + if o == "*" || strings.HasPrefix(o, "http://") || strings.HasPrefix(o, "https://") { + out = append(out, o) + } + } + return out +}