因为post是非简单请求会触发options预检,而get是简单请求不触发;中间件未正确处理options或响应头设置顺序错误,导致预检失败,浏览器拦截后续请求。

为什么前端发 POST 就报跨域,但 GET 正常?
因为 POST(尤其带 Content-Type: application/json 或自定义 header)属于「非简单请求」,浏览器会先发一次 OPTIONS 预检请求;而 GET 且无自定义头时是「简单请求」,不触发预检。你中间件没正确处理 OPTIONS,或响应头写在 c.Next() 前,导致预检失败——浏览器直接拦截后续请求,控制台只显示「Response to preflight request doesn't pass access control check」。
常见错误包括:
- 中间件注册顺序错:放在
r.GET(...)之后,导致部分路由没走中间件 -
c.Header(...)写在c.Next()前,OPTIONS响应体为空但状态码不对(应为 204 或 200) -
Access-Control-Allow-Origin设为"*"却同时设了Access-Control-Allow-Credentials: "true",浏览器直接拒收
用 gin-contrib/cors 时怎么避免 AllowOrigins 配置失效?
这个库对 AllowOrigins 的匹配是精确字符串比对,不是通配符匹配。填 ["https://*.example.com"] 没用;填 ["*"] 在 AllowCredentials: true 时会被浏览器无视。
实操建议:
- 明确列出所有合法前端域名:
[]string{"https://admin.example.com", "http://localhost:3000"} - 若需动态判断(比如开发环境允许多个 localhost 端口),别用
gin-contrib/cors,改用AllowOriginFunc回调 - 启用
AllowCredentials: true时,必须关闭AllowAllOrigins,且AllowOrigins不能为空数组 - 示例配置片段:
config := cors.Config{ AllowOrigins: []string{"https://a.com", "https://b.net"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"}, AllowCredentials: true, MaxAge: 12 * time.Hour, }
自己手写最小 CORS 中间件要注意哪三件事?
核心就三步:响应预检、设头、放行。但每步都有硬性约束。
- 必须先检查
c.Request.Method == "OPTIONS",然后c.AbortWithStatus(204)(或200),不能c.JSON或c.String—— OPTIONS 要求响应体为空 - 所有
c.Header(...)必须在c.Next()之后统一设置,否则预检响应里缺头,或业务响应被重复设头 -
Access-Control-Allow-Origin不能写死"*";如果前端要带 cookie,得从c.Request.Header.Get("Origin")取值,再白名单校验后原样回写
生产环境跨域配置最容易被忽略的点
开发时用 "*" 和 AllowCredentials: false 能跑通,但上线后只要前端需要登录态(比如带 Cookie 或 Authorization header),就必须做 Origin 白名单校验——这是浏览器强制要求,绕不过。
另外两个隐形坑:
-
Access-Control-Max-Age设太大(如 172800 秒)会导致预检结果缓存过久,改了后端配置前端仍沿用旧缓存,调试困难 - 前端发请求时没带
credentials: 'include'(fetch)或withCredentials: true(axios),后端即使开了AllowCredentials也无效
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











