go 的 net/http 包默认不处理 cors,必须手动添加响应头、显式处理 options 预检请求,并确保 access-control-allow-origin 与 credentials 设置互斥;三者缺一不可。

Go 的 net/http 包默认完全不处理 CORS,浏览器看到响应里没 Access-Control-Allow-Origin 就直接拦截请求——不是后端没收到,是请求压根没发出去。必须手动加头、必须处理 OPTIONS 预检、必须匹配凭证设置,三者缺一不可。
为什么 OPTIONS 请求总返回 405 或直接 404?
因为 Go 默认的 http.ServeMux 对 OPTIONS 方法无路由匹配逻辑,也不调用你的 handler。它不会自动转发或忽略,而是直接拒掉。
- 所有跨域接口前必须包裹中间件,且该中间件需在
r.Method == "OPTIONS"时提前w.WriteHeader(http.StatusNoContent)并return,不能继续执行next.ServeHTTP() - 别把中间件套在
http.ServeMux实例上再传给http.ListenAndServe;要套在最终业务 handler 上,比如corsMiddleware(http.StripPrefix("/api", apiHandler)) - 如果用了
gorilla/mux,确保handlers.CORS()是最外层包装器,而不是只包某个子路由
Access-Control-Allow-Origin 设为 * 却报错“credentials mode is 'include'”?
这是生产环境最常踩的坑:只要前端 fetch 带了 { credentials: 'include' }(或 jQuery 的 xhrFields: { withCredentials: true }),后端就不能用 *,必须指定确切域名。
-
Access-Control-Allow-Credentials: "true"和Access-Control-Allow-Origin: "*"同时存在 → 浏览器直接拒绝,控制台报错明确指出冲突 - 正确做法是读取请求头
Origin,白名单校验后原样回写:w.Header().Set("Access-Control-Allow-Origin", origin) - 白名单不能写
https://*.example.com—— 浏览器不认通配符子域名,只能列全[]string{"https://a.example.com", "https://b.example.com"}
用 gorilla/handlers.CORS 时为什么预检还是失败?
表面配置齐全,但两个关键参数不联动就会静默失效:允许凭证和允许源必须成对约束,且 AllowedHeaders 必须包含前端实际发送的自定义头。
-
handlers.AllowedOrigins([]string{"*"})+handlers.ExposedHeaders([]string{"X-Total-Count"})不够,漏了handlers.AllowCredentials()→ 带 cookie 的请求被拦 -
handlers.AllowedHeaders([]string{"Content-Type"})但前端发了X-Auth-Token→ 预检 403,浏览器提示 “Request header field x-auth-token is not allowed by Access-Control-Allow-Headers” - 若前端用
fetch()发送 JSON,Content-Type: application/json必须显式列入AllowedHeaders,否则预检不过
要不要引入第三方 CORS 库?
小项目手写中间件更轻量可控;中大型服务建议用 rs/cors 或 gin-contrib/cors(如果你用 Gin),它们已处理了 Max-Age 缓存、Vary: Origin、动态 Origin 回调等边界情况。
- 手写中间件适合固定几个前端域名、无需复杂策略的场景,代码不到 20 行,易审计
-
rs/cors支持AllowedOriginsFunc回调,可用于多租户动态域名匹配,比硬编码白名单灵活 - 切忌混用:不要在中间件里设一遍头,又在 handler 里再调
w.Header().Set()—— 后设覆盖前设,可能意外覆盖Access-Control-Allow-Credentials
真正难的不是加那几行头,而是理解浏览器预检机制如何与 Go 的无状态 HTTP 处理模型交互。一个没返回 StatusNoContent 的 OPTIONS,或一个漏掉的自定义请求头,就足以让整个跨域链路卡死,且错误信息分散在 Network 面板和 Console 里,得两边对照着看。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











