referer白名单校验需解析url取host比对域名,允许协议/路径差异;限流应基于ip或用户维度用rate.limiter或redis原子计数,且referer校验须置于限流之后以避免绕过。

Referer 白名单校验怎么做
Referer 校验是防盗链最常用手段,但不能只做字符串精确匹配 —— http://example.com 和 https://example.com/ 都该放行,而空 Referer 或伪造值必须拦截。
- 先用
url.Parse()解析 Referer 字符串,检查u.Host是否非空且不为空字符串;解析失败或 Host 为空直接拒绝 - 白名单用域名列表(如
[]string{"example.com", "admin.example.com"}),比对时只取u.Host,忽略协议、路径、端口 - 允许空 Referer 的场景(如直接访问、书签、某些 App 内嵌 WebView)需单独配置开关,别一刀切拦截
- 别忘了加
c.AbortWithStatusJSON(http.StatusForbidden, ...)并 return,否则后续逻辑仍会执行
为什么 Gin 里没有“防抖中间件”
防抖(debounce)是前端 JS 的定时合并逻辑,服务端收不到“待触发”状态,所有请求都是独立到达的 HTTP 请求 —— 所谓“防刷”,本质是限流,不是防抖。
- 用
golang.org/x/time/rate.Limiter做令牌桶限流,rate.Every(time.Second/5)表示每 200ms 放一个令牌,等价于rate.Limit(5),别写成time.Second/10误以为是 10 QPS - 全局限流适合压测防护,但生产环境必须按维度隔离:IP 用
c.ClientIP(),登录用户用c.GetString("user_id"),且认证中间件必须在限流之前注册,否则user_id为空 -
sync.Map缓存每个维度的*rate.Limiter实例,但要配过期清理(比如 5 分钟无访问就 delete),否则内存泄漏
多实例部署时如何避免限流失效
单机 rate.Limiter 在负载均衡后完全不可靠:每个实例只统计自己收到的请求,总量失控。
- 必须上 Redis + Lua 原子计数,key 设计要带维度和路径,例如
rate:ip:<code>c.ClientIP():api/v1/submit或rate:uid:<code>uid:upload - Lua 脚本里用
INCR+EXPIRE组合实现带过期的计数,避免 key 永久残留 - Redis 连接要用连接池,超时设短(如
DialTimeout: 500 * time.Millisecond),失败时降级为本地限流(用rate.Limiter)并打 warn 日志 - 别用
SETNX模拟令牌桶,精度差、并发高时易漏控
容易被忽略的 Referer 和限流组合坑
Referer 校验和限流看似独立,但顺序错、维度混,会导致策略形同虚设。
- Referer 中间件必须放在限流中间件之后 —— 否则恶意请求可能绕过 Referer 检查就耗尽令牌
- 同一资源(如图片接口)若同时启用 Referer 白名单和 IP 限流,要注意 NAT 环境下多个用户共用一个
c.ClientIP(),白名单放行后反而因 IP 限流误杀正常用户 - 静态资源路由(如
/static/*filepath)建议单独设宽松限流阈值(如 100 QPS),别跟 API 共用一套规则 - 日志里至少记录
c.ClientIP()、c.Request.Header.Get("Referer")、c.Writer.Status()三项,排查盗链或刷量时才可回溯











