gin 中 cors 与 ip 过滤必须拆分为两个独立中间件,且 cors 必须在 ip 过滤之前注册,仅添加响应头不中断流程;ip 过滤需基于正确解析的客户端真实 ip,并用 atomic.value 管理 cidr 规则切片以保证线程安全。

为什么 Gin 里不能把 CORS 和 IP 过滤混在一个中间件里
因为两者作用阶段和失败后果完全不同:CORS 是浏览器预检(OPTIONS)阶段的响应头控制,IP 过滤是服务端准入校验。如果在同一个中间件里先做 IP 拦截再写 CORS 头,被拒绝的请求根本不会走到 c.Header() 那一步;反过来,如果先写 CORS 头再拦截 IP,浏览器可能已放行 OPTIONS,但后续真实请求却被 403 拦住,前端看到的是“跨域成功但接口 403”,排查路径断裂。
正确做法是拆成两个独立中间件,并严格控制注册顺序:r.Use(corsMiddleware) 必须在 r.Use(ipFilterMiddleware) 之前,且 CORS 中间件不能提前 c.Abort() 或返回响应——它只负责加头,不阻断流程。
- CORS 中间件必须在所有路由注册前调用
r.Use() - IP 过滤中间件应在 CORS 之后、业务路由之前注册
- 若 IP 拒绝,应返回标准
403 Forbidden,而非444(后者会切断连接,导致浏览器无法收到 CORS 相关错误信息)
Gin 中提取真实客户端 IP 的常见错误写法
c.ClientIP() 在绝大多数生产环境直接失效——它默认只信任 127.0.0.1,而 Nginx、CDN、SLB 加了 X-Forwarded-For 后,这个函数返回的往往是代理内网地址(比如 10.0.1.5),不是用户真实出口 IP。
真正安全的做法是结合 SetTrustedProxies + 手动解析头字段:
- 先调用
engine.SetTrustedProxies([]string{"100.64.0.0/10", "192.168.0.0/16"}),网段必须精确匹配你实际使用的代理(如阿里云 SLB 是100.64.0.0/10,腾讯云 CLB 是100.64.0.0/11) - 再用
c.Request.Header.Get("X-Forwarded-For")取值,按逗号分割后从左往右遍历,对每个 IP 调用net.ParseIP(),跳过私有地址(127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16等)和链路本地 IPv6 地址 - 绝对不要直接字符串比较,IPv6 带方括号(
[2001:db8::1])需先去除
黑白名单数据结构选型:为什么 map[string]bool 不够用
单 IP 字符串匹配看似简单,但无法处理 CIDR 网段(如 192.168.1.0/24),更严重的是并发读写时需加锁,热更新时替换整个 map 会导致中间件正在执行的请求看到旧规则和新规则混杂的中间态。
推荐方案是用 net.IPNet 切片配合 atomic.Value:
- 预解析所有规则为
*net.IPNet,存入[]*net.IPNet切片 - 用
atomic.Value包裹该切片,更新时Store()整个新切片,读取时Load().([])*net.IPNet,零拷贝且线程安全 - 匹配逻辑:对每个
*net.IPNet调用ipNet.Contains(clientIP),优先查黑名单(命中即拒),再查白名单(未命中即拒) - 豁免路径(如
/healthz)必须在 IP 校验前判断,避免规则加载期间探针失败
Nginx 层与 Gin 层 IP 控制要不要重复做
要,但分工不同:Nginx 做粗粒度网关拦截(如封禁整个恶意 ASN),Gin 做细粒度业务级控制(如仅限制某个 API 路径的白名单)。两者不互斥,而是纵深防御。
关键注意点:
- Nginx 的
allow/deny规则必须放在location块内,不能写在http全局块;HTTPS 和 HTTP 的server块需分别配置 - 若 Nginx 已用
geo模块做了高性能黑名单,Gin 层就不要再对同一 IP 做重复检查,否则浪费 CPU - 当 Nginx 配置了
real_ip_header X-Forwarded-For和set_real_ip_from,Gin 就不能再依赖原始X-Forwarded-For,而应以c.ClientIP()为准——前提是SetTrustedProxies配置与 Nginx 的set_real_ip_from完全一致
最易被忽略的是代理链长度和头字段可信边界:多一层 CDN,就要多确认一层代理 IP 是否在 SetTrustedProxies 里,漏一个,真实 IP 就不可信。











