必须使用 gin-contrib/cors,不可手写 c.header();因其自动处理 options 预检、校验 origin 白名单、避免 allow-credentials 与 * 冲突、精准控制响应头时机,并支持 nginx 协同透传。

直接用 gin-contrib/cors,别手写 c.Header() —— 预检(OPTIONS)漏处理、Allow-Credentials 和 "*" 冲突、header 设置时机错一次就白配,这些坑在上线前高频出现。
为什么手写中间件设 CORS header 极易失败
浏览器对带凭证(cookie / Authorization)的跨域请求有强校验:一旦 Access-Control-Allow-Origin 设为 "*",同时又开了 Access-Control-Allow-Credentials: true,响应会被静默丢弃,Network 面板里连完整响应体都看不到。
-
OPTIONS请求返回 404:手写中间件常忽略方法判断,没拦截OPTIONS就直接c.Next(),导致预检根本没走 Gin 路由 -
Access-Control-Allow-Headers漏字段:前端发了X-Auth-Token,但后端没列进AllowHeaders,预检直接 403 - header 设置过晚:在
c.JSON()或c.String()之后再调c.Header().Set(),完全无效 - 动态 origin 场景失控:比如从数据库查白名单,手写逻辑难覆盖并发、缓存、错误 fallback
GIN 必须用 gin-contrib/cors.New() 显式配置
cors.Default() 只适合本地调试;生产环境必须用 cors.New() 控制每个细节,否则要么裸奔,要么被浏览器拒收。
- 安装:
go get github.com/gin-contrib/cors - 注册位置:必须在
r := gin.Default()之后、任何r.GET()之前调用r.Use(cors.New(...)) -
AllowCredentials: true时,AllowOrigins必须是具体域名列表,如[]string{"https://myapp.com", "http://localhost:3000"},不能含"*" -
ExposeHeaders要显式列出前端 JS 实际要读的字段,比如"X-Total-Count"或"New-Token",没列的即使后端返回了也拿不到 -
MaxAge建议设为12 * 60 * 60(12 小时),避免每次请求都触发预检
Nginx 后面跑 Gin 时 header 会被清空
Gin 返回了正确的 Access-Control-Allow-Origin,但浏览器仍报错?大概率是 Nginx 拦截或覆盖了 header。Nginx 默认不透传自定义响应头,尤其当用了 add_header,还会清空上游已设的 header。
- 必须用
$sent_http_*变量透传,例如:add_header 'Access-Control-Allow-Origin' '$sent_http_access_control_allow_origin' always; -
location块里要加add_header ... always,否则子块会丢掉父块的 header -
OPTIONS请求不能由 Nginx 直接返回 204,必须透传给 Gin 处理,否则预检失败 - 检查 Nginx error log,如果出现
upstream sent no valid HTTP/1.0 header,说明 Gin 提前写了 body,破坏了 header 写入时机
gorilla/handlers 是 net/http 和 gorilla/mux 的稳态选择
如果你不用 Gin,而是原生 net/http 或 gorilla/mux,gorilla/handlers 是最轻量且覆盖完整的方案,它不依赖框架、无副作用、自动处理所有预检分支。
- 安装:
go get github.com/gorilla/handlers - 基础用法:
http.ListenAndServe(":8080", handlers.CORS(handlers.AllowedOrigins([]string{"https://myapp.com"}))(r)) -
AllowedOrigins([]string{"*"})会自动禁用handlers.Credentials(true),这是浏览器规范强制的,中间件已做校验 - 需要动态白名单?改用
handlers.AllowedOriginsFunc(func(origin string) bool { return inWhitelist(origin) }) - 暴露自定义 header?加
handlers.ExposedHeaders([]string{"X-Request-ID"})
真正容易被忽略的是:CORS 不是“加几个 header 就完事”,它是浏览器、服务端、反向代理三方协同的结果。任何一个环节 header 没透传、时机错位、值冲突,都会导致静默失败——而你看到的往往只是控制台里一行模糊的 “CORS policy” 报错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











