cors.default()在生产环境失效,因其默认不设access-control-allow-origin头且禁用凭据,导致预检失败或浏览器静默丢弃响应;必须改用cors.new(config)并精确配置alloworigins、allowcredentials等项。

为什么 cors.Default() 在生产环境会失效
直接用 cors.Default() 看似省事,但它默认允许所有源(Access-Control-Allow-Origin: "*"),同时禁用凭据支持(Access-Control-Allow-Credentials: "false")。一旦你前端设置了 withCredentials: true(比如要传 Cookie 或 Authorization header),浏览器就会拒绝响应——因为规范明确禁止 * 与 true 共存。
-
cors.Default()的AllowAllOrigins是 false,实际行为是“不设 Origin 头”,导致预检失败后静默丢弃请求 - 它返回的 OPTIONS 响应状态码是 200,但 RFC 要求必须是
204 No Content - 它不校验 Origin 是否在白名单内,开发能跑,上线就 403 或静默失败
cors.New(config) 的关键配置项怎么填
手动构造 config 才能真正控制行为。重点不是“全开”,而是匹配你的部署场景:
-
config.AllowOrigins:必须写具体地址,如[]string{"http://localhost:3000", "https://app.example.com"};不能写"*"+config.AllowCredentials = true -
config.AllowCredentials:设为true时,AllowOrigins必须是精确匹配,且前端 fetch 必须带credentials: "include" -
config.MaxAge:建议设为86400(24 小时),减少重复 OPTIONS 请求 -
config.AllowHeaders:至少包含"Content-Type,Authorization,X-Requested-With",否则自定义 header 会被拦
示例片段:
config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://localhost:3000", "https://prod.example.com"}
config.AllowCredentials = true
config.MaxAge = 86400
config.AllowHeaders = []string{"Content-Type", "Authorization", "X-Requested-With"}
router.Use(cors.New(config))
OPTIONS 预检请求没响应?检查中间件顺序和终止逻辑
这是最常被忽略的点:CORS 中间件必须放在所有路由注册之前,且对 OPTIONS 方法要立即终止链,不能继续往下走。
- 错误写法:
router.GET("/api/user", corsMiddleware(), handler)—— 中间件只作用于该路由,漏掉其他路径 - 错误写法:在中间件里只设 Header,没处理
OPTIONS就直接c.Next(),导致后续 handler 返回 404 或 500 - 正确做法:在中间件里判断
c.Request.Method == "OPTIONS",然后c.AbortWithStatus(204),不调用c.Next()
注意:c.AbortWithStatus(200) 是错的,必须用 204;c.JSON(204, nil) 也不行,会带空 body,应纯 header 响应。
开发 vs 生产:Origin 白名单怎么动态适配
硬编码 AllowOrigins 在多环境部署时很麻烦。更稳妥的方式是运行时解析 Origin header 并做白名单比对:
- 从
c.Request.Header.Get("Origin")取值,空字符串或未设置时跳过 CORS 头(避免暴露内部地址) - 白名单用 map 或 slice 存,避免正则或模糊匹配引入安全风险
- 生产环境建议把白名单从配置文件或环境变量注入,而不是写死在代码里
- 如果需支持子域名(如
app1.example.com、app2.example.com),可用strings.HasSuffix(origin, ".example.com"),但务必验证协议和端口
复杂点在于:Origin 可能为空(比如 curl 测试)、可能被伪造、也可能含端口(http://localhost:3000 ≠ http://localhost)。这些边界情况不处理,上线后第一波用户请求就挂。











