因为浏览器禁止在 Access-Control-Allow-Credentials: true 时使用 Access-Control-Allow-Origin: "*",必须动态回传合法 Origin 或白名单匹配,同时正确处理 OPTIONS 预检并确保中间件注册顺序正确。

为什么设置了 Access-Control-Allow-Origin: "*",带 Cookie 的 AJAX 请求还是失败?
因为浏览器明确禁止在 Access-Control-Allow-Credentials: true 时使用通配符 "*" 作为 Access-Control-Allow-Origin 值。一旦你调用 ctx.SetCookie() 或前端设置 xhrFields: { withCredentials: true },就必须把 Origin 动态回传,不能写死 "*"。
常见错误现象:
- 控制台报错:
Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include' - POST/PUT 等非简单请求失败,但 GET 能通(因 GET 可能绕过预检)
- Cookie 明明已 set,但后续请求里
ctx.Request.Cookie("xxx")返回nil
实操建议:
- 中间件中必须读取
r.Header.Get("Origin"),并原样设为Access-Control-Allow-Origin值 - 务必同时设置
Access-Control-Allow-Credentials: "true" - 确保该中间件注册在所有路由 之前,且不能在
c.Next()前 abort 或写响应
如何安全地支持多个指定前端域名(如 localhost:3000 和 prod.example.com)?
不能靠数组匹配或模糊通配,gin-contrib/cors 对 AllowOrigins 是精确字符串比对,["https://*.example.com"] 无效;而自己写的中间件若不做白名单校验,直接回传 Origin 就可能被恶意站点利用。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 维护一个合法 Origin 列表,例如:
[]string{"http://localhost:3000", "https://app.example.com", "https://admin.example.com"} - 在中间件中解析
r.Header.Get("Origin"),检查是否在白名单内;不在则不设Access-Control-Allow-Origin,或返回 403 - 若 Origin 为空(比如 curl 直接请求),可跳过 CORS 头设置,避免干扰非浏览器客户端
- 注意:HTTP 协议下
localhost和127.0.0.1视为不同源,调试时需同时加入
SetCookie 参数填错导致 Cookie 不发送或无法读取
最常踩的坑是 Domain 和 Secure 组合错误,尤其本地开发时用 HTTP 协议却设了 Secure: true,或生产环境漏写 Domain 导致 Cookie 被浏览器丢弃。
参数差异与影响:
-
Domain:本地调试填"localhost"即可;上线必须填一级域名(如"example.com"),填"www.example.com"会导致子域间无法共享 -
Secure:仅 HTTPS 环境设true;HTTP 下设true→ Cookie 不发送 -
HttpOnly:设true可防 XSS 读取 Cookie,但 JS 也就拿不到它 —— 如果你依赖 JS 读取(如做登出逻辑),就得权衡 -
Path:设"/"才能在所有路径下携带;设"/api"则只有/api/xxx请求才带
示例正确写法(本地调试):
ctx.SetCookie("session_id", token, 3600, "/", "localhost", false, true)
示例正确写法(生产环境):
ctx.SetCookie("session_id", token, 3600, "/", "example.com", true, true)
OPTIONS 预检请求没处理好,导致 POST 请求直接被拦截
浏览器在发带凭证的非简单请求前,会先发一个 OPTIONS 请求探路。如果中间件没响应这个请求,或响应状态码不是 200/204,整个链路就断了——你甚至看不到后端日志里有 POST 记录。
关键点:
- 必须显式处理
r.Method == "OPTIONS"场景,并立即w.WriteHeader(http.StatusNoContent)或204 - 不能在 OPTIONS 里调用
c.Next(),否则会走到后续 handler,可能 panic 或返回 404 - 响应头必须包含
Access-Control-Allow-Methods和Access-Control-Allow-Headers,否则浏览器认为“服务不支持” - 如果你用了
gin-contrib/cors,它默认处理 OPTIONS;但自定义中间件必须手动覆盖
最小可用片段(含预检):
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
r := c.Request
w := c.Writer
origin := r.Header.Get("Origin")
if origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With")
}
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
c.Next()
}
}
前端必须配 withCredentials: true,后端 Cookie 才会随请求发出;反过来,后端没设 Access-Control-Allow-Credentials,前端即使设了也白搭。这两边漏掉任意一环,认证流程就卡死。


















