因为浏览器严格禁止 access-control-allow-origin: * 与 access-control-allow-credentials: true 同时存在,当请求携带凭证(如 credentials: 'include')时,服务端必须返回精确匹配的源(如 https://example.com)并显式设置 access-control-allow-credentials: true,否则浏览器静默拦截响应。

为什么 c.Header("Access-Control-Allow-Origin", "*") 在带凭证时会失败
浏览器明确禁止 Access-Control-Allow-Credentials: true 和 Access-Control-Allow-Origin: "*" 同时存在。一旦后端返回这两个头,浏览器直接丢弃整个响应,前端连 status 都收不到,控制台只显示「Response to preflight request doesn't pass access control check」。
常见误操作是:开发时图省事写 "*",又开了 withCredentials: true(Vue 的 axios.defaults.withCredentials = true 或 fetch 的 credentials: "include"),结果 POST/PUT 请求全挂,但 GET 偶尔能通——因为简单请求不触发预检,而带 body 或自定义 header 的请求必走 OPTIONS,这时就暴露问题。
- 必须显式列出允许的 Origin,如
[]string{"https://admin.example.com", "https://app.example.com"} - 如果前端端口不同(如
localhost:3000vslocalhost:8080),也要分别写全 - 协议、域名、端口缺一不可,
"http://localhost"和"http://localhost:3000"是两个不同源
gin-contrib/cors 的 AllowOriginFunc 怎么动态校验 Origin
硬编码域名列表在多环境或 SaaS 场景下很快就不够用。AllowOriginFunc 是唯一支持运行时判断的入口,比改配置更灵活,也比自己手写中间件少踩坑。
它会在每次请求时被调用,传入原始 Origin header 值(如 "https://tenant-a.myapp.com"),返回 true 才放行。注意:这个函数在 OPTIONS 预检和真实请求中都会执行。
- 别在里面做耗时操作(比如查数据库),否则每个请求都卡顿
- 空 Origin(如 file:// 协议或某些 curl 测试)会传空字符串
"",需显式处理 - 如果用子域名白名单,可用
strings.HasSuffix(origin, ".myapp.com"),但注意不能匹配evilmyapp.com - 示例:
AllowOriginFunc: func(origin string) bool { return origin == "https://a.com" || origin == "https://b.net" }
OPTIONS 预检失败的三个典型信号
不是所有跨域问题都表现为红字报错。真正卡在预检阶段时,你可能只看到一个 204 或 0 字节响应,Network 面板里 OPTIONS 请求状态码是 200/204,但后续请求根本没发出去。
检查点很具体:
- Chrome DevTools 的 Network → 点开 OPTIONS 请求 → 查看 Response Headers 是否含
Access-Control-Allow-Origin;没有?说明中间件没生效或顺序错了 - Response Headers 有
Access-Control-Allow-Origin,但值是"*"且你前端用了 credentials → 浏览器静默拦截 - OPTIONS 请求返回 404 或 500 → 说明路由没注册处理 OPTIONS 的 handler,或者中间件在
c.Next()前 abort 了
中间件注册顺序为什么必须在 router.GET 之前
router.Use(corsMiddleware) 必须紧接在 gin.Default() 或 gin.New() 之后、任何 router.XXX() 调用之前。Gin 的中间件链是线性构建的,注册晚了,那些已注册的路由就压根不会经过 CORS 中间件。
典型错误写法:router.GET("/api"); router.Use(cors) —— 这时 /api 路由已经固化,CORS 中间件对它完全无效。
- 正确顺序:先
Use,再GET/POST等注册路由 - 如果你用了
gin-contrib/cors,它的New或Default返回的是中间件函数,不是配置对象,别误当成配置传错位置 - 调试时可在中间件里加
log.Println("CORS hit:", c.Request.URL.Path),确认是否被调用
add_header,且确保 always 参数开启,否则 4xx/5xx 响应不会带这些头。











