结论:别手写中间件,用 gin-contrib/cors,但必须按场景配参数,否则 OPTIONS 404 或 Access-Control-Allow-Origin 不生效;因其需严格匹配 AllowOrigins 与 AllowCredentials、完整覆盖 AllowHeaders、正确注册顺序及预检响应状态,手写易漏关键逻辑导致预检失败。

直接说结论:别手写中间件,用 gin-contrib/cors,但必须按场景配参数,否则 OPTIONS 404 或 Access-Control-Allow-Origin 不生效。
为什么手写中间件容易出错
很多人抄一段几行的 CorsMiddleware 就往 r.Use() 里塞,结果前端还是报 Response to preflight request doesn't pass access control check。根本原因不是代码没跑,而是漏了三件事:没处理 OPTIONS 请求的提前返回、没设 Access-Control-Allow-Credentials 却又开了 credentials: 'include'、或者把 AllowOrigins 设成 * 却又要求带凭据——这两者互斥。
手写中间件看似简单,实则要覆盖:Origin 校验逻辑、OPTIONS 短路响应、所有 Access-Control- 头的组合规则。稍一疏忽,预检就失败。
- 浏览器对
credentials: true的请求,Access-Control-Allow-Origin不能是*,必须是精确域名 -
AllowHeaders必须包含前端实际发的自定义头(比如Authorization、X-Trace-ID),漏一个,预检就挂 - 中间件注册顺序很重要:必须在
router.Use()中最早注册,否则路由匹配后才走 CORS,OPTIONS可能 404
gin-contrib/cors 的三种用法区别
go get github.com/gin-contrib/cors 后,它提供三个入口函数:Default、DefaultConfig、New。别图省事全用 Default(),它只设了 AllowAllOrigins: true 和基础方法,其他全默认,生产环境基本不可用。
立即学习“go语言免费学习笔记(深入)”;
-
cors.Default():等价于cors.New(cors.Config{AllowAllOrigins: true}),适合本地开发调试,但禁用凭据、不支持自定义 header,上线前必须换 -
cors.DefaultConfig():返回一个预设 config 结构体,字段可改,但得自己赋值再传给New,不如直接用New -
cors.New(config):唯一推荐方式,显式控制每个字段,避免隐式行为。例如前端是https://app.example.com,后端要带 cookie,就得写清楚
示例(生产常用):
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://app.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
AllowCredentials: true,
ExposeHeaders: []string{"Content-Length"},
MaxAge: 12 * time.Hour,
}))常见错误现象与对应修复点
遇到跨域报错,先看浏览器 Network 面板里 OPTIONS 请求的 Response Headers 有没有 Access-Control-Allow-Origin,没有就说明中间件根本没生效;有但值不对,说明配置逻辑错了。
-
Failed to load ... No 'Access-Control-Allow-Origin' header is present:中间件没注册,或注册位置太靠后(比如在r.Group()之后才调Use),或AllowOrigins为空且没设AllowAllOrigins: true -
Request header field authorization is not allowed by Access-Control-Allow-Headers:AllowHeaders漏了Authorization,补上即可 -
Credentials flag is 'true', but the 'Access-Control-Allow-Origin' header value is '*':删掉*,换成具体域名,或干脆关掉AllowCredentials(如果前端不需要 cookie) -
OPTIONS返回 404:路由没匹配到,检查中间件是否在router实例上调用,而不是某个子Group上;也可能是 Gin 版本太老(OPTIONS 自动路由未启用,需手动加r.OPTIONS("/path", handler)
开发 vs 生产环境的配置差异
本地开发时前端常跑在 http://localhost:3000,后端在 http://localhost:8080,此时用 AllowAllOrigins: true 最省事。但上线后必须锁定 AllowOrigins,否则任意网站都能调你接口。
- 开发环境:可设
AllowOrigins: []string{"http://localhost:3000", "http://127.0.0.1:3000"},允许本地多个端口 - 测试环境:对接 QA 前端域名,如
"https://qa-app.example.com" - 生产环境:严格只写上线域名,且建议用
AllowOriginFunc做白名单校验,比如只允许可信子域名
AllowOriginFunc 示例(比静态列表更灵活):
AllowOriginFunc: func(origin string) bool {
return strings.HasSuffix(origin, ".example.com") || origin == "https://example.com"
}注意:一旦设了 AllowOriginFunc,AllowOrigins 就被忽略,别两个都写。
最易被忽略的点是:CORS 是浏览器强制执行的,服务端返回的响应头对 curl、Postman 无效;调试时务必用真实页面发起请求,别只靠工具测。


















