直接用 rate.limiter 会误伤正常用户,因 nat 下多用户共享 ip、未按 ip 或 user_id 隔离实例、未清理 sync.map 缓存、未设可信代理导致伪造 x-forwarded-for 绕过,且 basicauth 明文密码暴露风险高,referer 校验易误判,上传校验须依赖 magic number 而非后缀。

为什么直接用 rate.Limiter 会误伤正常用户
全局限流(比如对所有请求统一限 5 QPS)在压测或后台接口兜底时有用,但放到用户-facing 接口上极易误杀——NAT 环境下几十人共用一个公网 IP,c.ClientIP() 返回的可能是代理地址,导致整个办公室被封。更糟的是,rate.Limiter 实例若不按维度隔离(如每个 IP 单独一个),多个用户会互相挤占令牌桶配额。
- 必须为每个真实客户端 IP 或已认证的
user_id维护独立的*rate.Limiter实例 -
sync.Map可缓存 limiter,但 key 是字符串(如c.ClientIP()),且必须加定时清理逻辑,否则内存持续增长 - 没配
router.SetTrustedProxies()就直接读X-Forwarded-For,攻击者伪造头就能绕过 - 限流中间件必须注册在认证中间件之后,否则
c.GetString("user_id")为空,退化成 IP 限流
gin.BasicAuth 不能直接写密码明文的原因
硬编码 gin.Accounts{"admin": "123456"} 或从 os.Getenv() 直读密码,等于把凭证挂在进程环境里:ps aux 或 /proc/$PID/environ 都能直接看到。Gin 虽在 1.19+ 默认用 subtle.ConstantTimeCompare 防时序攻击,但凭据本身暴露就已失效。
- 密码应从权限为
600的本地 YAML/JSON 文件加载,文件路径不进 Git - 若必须走环境变量,加载后立即调
os.Unsetenv("BASIC_AUTH_PASS") -
gin.BasicAuth不自动注入用户信息,需用c.MustGet(gin.AuthUserKey).(string)手动取当前用户名 - 路由必须挂到
r.Group("/api", middleware)下,单独给 handler 加无效
Referer 白名单校验容易忽略的解析陷阱
c.Request.Referer() 返回空字符串很常见——浏览器隐私策略、HTTPS→HTTP 跳转、或前端主动清除 referer 都会导致。直接判空返回 403 会误拒合法请求;而只比对 host 又可能漏掉子域名或端口差异。
- 先检查
referer == "",但不要直接拦截,可记录日志并放行(或按业务决定是否宽松处理) -
url.Parse(referer)后必须验证u.Host != "",否则u.Host == "a.com/bad.js"这种非法值会被当合法 host - 白名单应包含完整 host(如
"api.example.com"),不建议用strings.HasSuffix(u.Host, ".example.com"),易被evil.example.com绕过 - 若业务允许子域名,用
net.SplitHostPort提取 host 后再做strings.HasSuffix,并排除端口干扰
上传接口防木马的关键不是文件扩展名
仅靠检查 file.Filename 后缀(如 ".jpg")完全不可靠——攻击者改个后缀就能绕过。真正有效的是读文件头部字节(magic number)+ 限制 MIME 类型 + 内存中解码校验图片结构。
- 用
c.FormFile("file")获取*multipart.FileHeader后,调file.Open()得到io.Reader - 读前 512 字节,用
http.DetectContentType()判定实际 MIME,只允许"image/jpeg"、"image/png"等白名单类型 - 对图片类型,用
image.DecodeConfig()尝试解析尺寸,失败即拒收(可过滤伪装成图的 PHP 木马) -
router.MaxMultipartMemory必须设低值(如8 ),防大文件耗尽内存
c.ClientIP() 和 c.Request.Header.Get("X-Forwarded-For") 返回的几乎全是内网地址。这个细节线上一错,整套防护就形同虚设。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











