# Go 后端 CORS 配置示例 ## 问题说明 开发环境中,前端运行在 `http://localhost:5173`,后端运行在 `http://localhost:8080`。 由于跨域限制,需要在后端配置 CORS 才能正常访问 API。 ## 使用 Gin 框架 ### 1. 安装 CORS 中间件 ```bash go get github.com/gin-contrib/cors ``` ### 2. 配置 CORS ```go package main import ( "github.com/gin-gonic/gin" "github.com/gin-contrib/cors" "time" ) func main() { r := gin.Default() // 开发环境:配置 CORS if gin.Mode() == gin.DebugMode { config := cors.Config{ AllowOrigins: []string{"http://localhost:5173"}, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, } r.Use(cors.New(config)) } // API 路由 api := r.Group("/api/v1") { // 图片相关 api.GET("/images", getImages) api.GET("/image/today/meta", getTodayImageMeta) api.GET("/image/date/:date/meta", getImageMetaByDate) api.GET("/image/random/meta", getRandomImageMeta) // 管理员相关 api.POST("/admin/login", adminLogin) // ... 其他路由 } // 生产环境:静态文件服务 if gin.Mode() == gin.ReleaseMode { r.Static("/assets", "./web/assets") r.StaticFile("/", "./web/index.html") // SPA fallback r.NoRoute(func(c *gin.Context) { c.File("./web/index.html") }) } r.Run(":8080") } ``` ### 3. 更灵活的 CORS 配置 ```go // 根据环境变量动态配置 func setupCORS(r *gin.Engine) { allowOrigins := os.Getenv("ALLOW_ORIGINS") if allowOrigins == "" { allowOrigins = "http://localhost:5173" } origins := strings.Split(allowOrigins, ",") config := cors.Config{ AllowOrigins: origins, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, } r.Use(cors.New(config)) } func main() { r := gin.Default() // 只在开发环境启用 CORS if gin.Mode() == gin.DebugMode { setupCORS(r) } // ... 其他配置 } ``` ## 使用标准库 如果不使用 Gin 框架,可以手动实现 CORS: ```go package main import ( "net/http" ) func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 设置 CORS 头 w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Max-Age", "43200") // 12 hours // 处理预检请求 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } func main() { mux := http.NewServeMux() // 注册路由 mux.HandleFunc("/api/v1/images", getImages) // ... 其他路由 // 包装 CORS 中间件 handler := corsMiddleware(mux) http.ListenAndServe(":8080", handler) } ``` ## 配置文件方式 可以通过配置文件管理 CORS 设置: ```yaml # config.yaml server: port: 8080 mode: debug # debug 或 release cors: enabled: true allow_origins: - http://localhost:5173 - http://127.0.0.1:5173 allow_methods: - GET - POST - PUT - PATCH - DELETE - OPTIONS allow_headers: - Origin - Content-Type - Accept - Authorization allow_credentials: true max_age: 43200 ``` ```go type CORSConfig struct { Enabled bool `yaml:"enabled"` AllowOrigins []string `yaml:"allow_origins"` AllowMethods []string `yaml:"allow_methods"` AllowHeaders []string `yaml:"allow_headers"` AllowCredentials bool `yaml:"allow_credentials"` MaxAge int `yaml:"max_age"` } func setupCORSFromConfig(r *gin.Engine, cfg *CORSConfig) { if !cfg.Enabled { return } config := cors.Config{ AllowOrigins: cfg.AllowOrigins, AllowMethods: cfg.AllowMethods, AllowHeaders: cfg.AllowHeaders, AllowCredentials: cfg.AllowCredentials, MaxAge: time.Duration(cfg.MaxAge) * time.Second, } r.Use(cors.New(config)) } ``` ## 安全建议 1. **生产环境不要启用 CORS**:生产环境前后端在同一域名下,不需要 CORS 2. **限制允许的源**:不要使用 `*`,明确指定允许的域名 3. **使用环境变量**:允许的源应该通过环境变量配置,不要硬编码 4. **限制方法和头**:只允许必要的 HTTP 方法和请求头 5. **注意凭证**:如果使用 `AllowCredentials: true`,不能使用通配符源 ## 测试 CORS 配置 可以使用 curl 测试 CORS: ```bash # 测试预检请求 curl -X OPTIONS http://localhost:8080/api/v1/images \ -H "Origin: http://localhost:5173" \ -H "Access-Control-Request-Method: GET" \ -v # 测试实际请求 curl -X GET http://localhost:8080/api/v1/images \ -H "Origin: http://localhost:5173" \ -v ``` 应该能看到响应头中包含: ``` Access-Control-Allow-Origin: http://localhost:5173 Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization Access-Control-Allow-Credentials: true ``` ## 常见问题 ### Q: 为什么要区分开发和生产环境? A: 生产环境前后端部署在同一域名下,不需要 CORS。只在开发环境需要。 ### Q: 可以使用代理吗? A: 可以在 Vite 中配置代理,但不推荐。直接配置 CORS 更接近生产环境,便于发现问题。 ### Q: 如何处理认证? A: 如果使用 Cookie 或需要发送凭证,必须设置 `AllowCredentials: true` 和明确的源。 ### Q: 预检请求是什么? A: 浏览器在某些跨域请求前会发送 OPTIONS 请求,询问服务器是否允许该跨域请求。