网关层必须拦截options预检请求并返回204,allowcredentials为true时allowedorigins不可用“*”,cors中间件须在路由注册前全局启用,且仅限网关层配置以避免响应头重复或缓存问题。

网关层必须拦截 OPTIONS 请求,不能转发
浏览器发起带 credentials 或自定义 Header 的跨域请求前,必先发 OPTIONS 预检。如果 Gin 网关没拦截、直接把 OPTIONS 转给下游服务,而下游没注册该路由,就会返回 405 Method Not Allowed 或 404 Not Found——前端控制台只显示 “CORS error”,连哪条响应头缺失都看不到。
实操建议:
-
gin-contrib/cors默认会拦截并返回http.StatusNoContent(204),但需确认没在下游服务重复挂载同一中间件,否则响应头被写两次会触发浏览器报错 - 手写中间件时,
if c.Request.Method == "OPTIONS"分支必须c.AbortWithStatus(http.StatusNoContent)后立即return,绝不能调用c.Next() - 若用
rs/cors,默认OptionsPassthrough: true,务必显式设为false
AllowCredentials = true 时,AllowedOrigins 不能是 "*"
这是浏览器硬性限制,不是后端能绕过的。一旦你设了 Access-Control-Allow-Credentials: "true"(注意:是字符串 "true",不是布尔值),同时 Access-Control-Allow-Origin 是 "*",整个响应会被静默丢弃——前端请求卡在 pending,控制台甚至不报错。
实操建议:
- 开发环境可用白名单:
[]string{"http://localhost:3000", "http://127.0.0.1:3000"} - 生产环境建议从
Origin头提取域名,做白名单校验后再回写,避免反射型 XSS -
gin-contrib/cors不支持"https://*.example.com"这类子域名通配,必须列全:[]string{"https://app.example.com", "https://admin.example.com"} - 响应中必须含
Vary: Origin头(gin-contrib/cors自动加,手写需手动补)
Gin 中间件注册顺序直接影响 CORS 是否生效
中间件必须在任何路由注册前调用 r.Use(),否则对已注册的路由不生效。常见错误是先写 r.GET() 再 r.Use(corsMiddleware),结果只有后续路由受控,关键接口反而没跨域头。
实操建议:
- 正确顺序:
r := gin.Default()→r.Use(Cors())→r.GET(...)→r.Run() - 中间件内部不能在
c.Next()前写响应头(比如提前c.Header()),否则OPTIONS预检可能因状态码或响应体非空而失败 - 若用了
gin-contrib/cors,别混用自己写的 CORS 中间件,冲突会导致Access-Control-Allow-Origin被写两次,浏览器拒绝解析
微服务架构下,CORS 只应在网关层配置,不该分散到每个 Go 服务
后端服务间调用走内网,不经过浏览器,根本不需要 Access-Control-Allow-Origin。每个服务都加 CORS,不仅没用,还可能把 /healthz、/metrics 等管理接口暴露给公网。
实操建议:
- 确认所有下游 Go 服务关闭 CORS 中间件,只留网关一层统一处理
- 若本地开发时前端直连某个 Go 服务(如
http://localhost:3000 → http://localhost:8080),才需在该服务启用,且必须严格校验Origin - 云环境优先用网关能力:AWS API Gateway、Kong、Traefik 的 CORS 插件更可靠,还能联动 WAF 和日志
OPTIONS 返回了全部头,若后端响应中漏了 Vary: Origin,CDN 或代理可能缓存了针对某个 Origin 的响应,再换 Origin 就会返回错的 Access-Control-Allow-Origin。这个坑不看网络面板几乎发现不了。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











