生产环境禁用 * 配置 Access-Control-Allow-Origin,因其导致 credentials 失效且暴露全部接口;应通过环境变量动态注入白名单,配合非空校验与 AllowOriginFunc 实现灵活域名匹配,并确保 OPTIONS 预检请求被正确响应。

为什么不能直接用 * 配置 Access-Control-Allow-Origin
生产环境用 * 会导致 Access-Control-Allow-Credentials: true 失效,浏览器直接拒绝响应;同时暴露全部接口给任意域名,等于放弃源站校验。开发环境虽可临时用,但一旦开启 withCredentials(比如带 cookie 登录),请求立刻 401 或被静默拦截。
如何按环境动态控制 AllowOrigins 列表
硬编码列表或写死 []string{"http://localhost:3000", "https://prod.example.com"} 会导致配置与部署脱节——比如测试服加了个新域名,就得改代码、编译、上线。更稳妥的方式是把白名单从环境变量注入:
-
GIN_CORS_ORIGINS=dev.example.com,localhost:3000,127.0.0.1:3000→ 开发环境 -
GIN_CORS_ORIGINS=app.example.com,admin.example.com→ 生产环境 - 启动时用
strings.Split(os.Getenv("GIN_CORS_ORIGINS"), ",")解析,空值 fallback 到默认项(如仅允许本地)
注意:os.Getenv 返回空字符串时,别直接传给 cors.Config{AllowOrigins: []string{""}},会触发 Gin 的 panic —— 必须做非空校验。
AllowOriginFunc 比 AllowOrigins 更灵活的判断逻辑
当白名单需带通配符(如允许所有 staging-*.example.com)或依赖运行时状态(如从 Redis 动态查域名)时,AllowOrigins 数组就力不从心了。这时必须用 AllowOriginFunc:
立即学习“go语言免费学习笔记(深入)”;
- 函数签名必须是
func(origin string) bool,返回true才放行 - 内部可做子域名匹配:
strings.HasSuffix(origin, ".example.com") && !strings.HasPrefix(origin, "evil.") - 若依赖外部服务(如 DB 查询),务必加缓存和超时,否则每个预检请求都卡住
- 注意:该函数在每次请求(包括 OPTIONS)时都会执行,高频调用下要避免阻塞
OPTIONS 预检请求没被正确处理的典型表现
前端发 POST 带 Content-Type: application/json 或自定义 header 时,浏览器先发 OPTIONS 请求。如果 Gin 没提前响应它,就会卡在 pending 状态,控制台报 net::ERR_FAILED 或 Failed to fetch。
-
gin-contrib/cors默认已处理 OPTIONS,但如果你手写中间件,必须显式c.AbortWithStatus(http.StatusNoContent) - 别漏掉
Access-Control-Max-Age,否则每秒都发一次 OPTIONS —— 生产环境建议设为86400(24 小时) - 如果用了 Nginx 做反向代理,确认它没吞掉 OPTIONS 请求(检查
nginx.conf是否有if ($request_method = 'OPTIONS') { ... }且返回了正确头)
真正难调试的不是配置本身,而是白名单和预检响应的组合行为 —— 比如 AllowOriginFunc 返回 true,但没设置 Access-Control-Allow-Headers,浏览器照样拒绝后续请求。


















