应使用 gin-contrib/cors 而非手写中间件,因其能正确处理预检请求、避免 allow-credentials 与 * 冲突、精准设置响应头时机,并支持动态白名单及 nginx 协同配置。

直接用 gin-contrib/cors,别手写 c.Header() —— 预检(OPTIONS)漏处理、Allow-Credentials 和 * 冲突、header 设置时机错一次就白配,这些坑在上线前高频出现。
为什么不能自己写中间件设 header
手写 CORS 中间件看似可控,但实际极易出错:浏览器对带凭证(cookie / Authorization)的跨域请求有强校验,一旦 Access-Control-Allow-Origin 设为 "*",同时又开了 Access-Control-Allow-Credentials: true,响应会被浏览器静默丢弃,连 Network 面板都看不到完整失败原因。
常见错误现象:
Failed to load http://api.example.com/xxx: 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'- 前端
fetch(..., { credentials: 'include' })成功发出去,但response拿不到 body 或自定义 header - OPTIONS 请求返回 204 但后续请求仍被拦截——说明预检没被正确响应或没走 Gin
根本原因是:手写中间件很难覆盖所有分支(比如没判断 Method == "OPTIONS" 就直接 c.Next()),且无法自动注入 Access-Control-Allow-Headers 白名单校验、不处理 Max-Age 缓存逻辑、也不校验 Origin 是否在允许列表中。
如何正确配置 gin-contrib/cors
安装:go get github.com/gin-contrib/cors,然后在路由注册前调用 r.Use():
import "github.com/gin-contrib/cors"
<p>r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"<a href="https://www.php.cn/link/66b66f99472865d56557a0a42a4b4aba">https://www.php.cn/link/66b66f99472865d56557a0a42a4b4aba</a>", "<a href="https://www.php.cn/link/8e5687e2d6ab87e5da2f833f3e8986a4">https://www.php.cn/link/8e5687e2d6ab87e5da2f833f3e8986a4</a>"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
AllowCredentials: true,
ExposeHeaders: []string{"X-Total-Count", "New-Token"},
MaxAge: 12 <em> 60 </em> 60,
}))</p>
关键点:
-
AllowOrigins必须是完整协议+域名+端口,不支持通配符如"https://*.example.com";填"*"会和AllowCredentials: true冲突 -
ExposeHeaders要显式列出前端 JS 实际要读的字段,否则即使后端返回了也拿不到 -
MaxAge建议设为12 * time.Hour,避免每次请求都触发预检 - 必须放在
r.GET、r.POST等任何路由定义之前,否则中间件不生效
生产环境 Nginx 后面跑 Gin 怎么办
Gin 返回了正确的 CORS header,但浏览器仍报错?90% 是 Nginx 拦截或覆盖了响应头。
典型问题:
- Nginx 默认不透传自定义响应头,尤其用了
add_header后,会清空上游(Gin)已设的所有 header - OPTIONS 请求被 Nginx 直接返回 204,没透传给 Gin,导致预检失败
- 日志里出现
upstream sent no valid HTTP/1.0 header,说明 Gin 在写 header 前就写了 body(比如提前c.JSON)
Nginx 配置要点:
- 用
add_header ... always;,否则子 location 块会丢失父块 header - 确保
proxy_pass后没有其他add_header覆盖 CORS 头 - 禁止对 OPTIONS 做特殊处理,让它原样透传到 Gin
动态 origin 白名单怎么搞
如果允许的域名存在数据库或配置中心,不能硬编码在代码里,就别用 gin-contrib/cors 的默认配置。
改用 AllowOriginFunc 自定义判断逻辑:
config := cors.Config{
AllowOriginFunc: func(origin string) bool {
// 从 DB 或 cache 查 origin 是否在白名单
return isInWhitelist(origin)
},
AllowCredentials: true,
// 其他字段同上...
}
r.Use(cors.New(config))
注意:AllowOriginFunc 返回 true 时,Access-Control-Allow-Origin 响应头会设为当前请求的 origin 字符串(不是 *),所以依然兼容带凭证的请求。
真正容易被忽略的是:这个函数会在每次请求(包括 OPTIONS)时执行,如果查 DB 没加缓存或没做连接池复用,可能成为性能瓶颈。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











