5.17 接口限流
概述
接口防护采用固定窗口限流(middleware/ratelimit.go):按 IP + 路径计数,普通接口与认证入口双阈值。计数经 Redis 跨实例共享,存储异常时放行(限流不阻断业务)。
配置
yaml
rateLimit:
enable: true # 是否启用限流
limit: 120 # 普通接口每窗口每 IP 每路径最大请求数,0 关闭普通限流
windowSeconds: 60 # 计数窗口(秒)
authLimit: 10 # 登录/验证码/令牌兑换每窗口每 IP 最大请求数(更严格)规则明细
text
作用范围:
* 仅限 /api 前缀路径;/api/health 显式豁免
* /api.json、/swagger、静态资源不带 /api/ 前缀,自然不受限
双阈值:
* 普通接口:limit(默认 120 次/窗口/IP/路径)
* 认证入口(/api/login、/api/captcha、/api/oauth2/token):authLimit(默认 10 次/窗口/IP)
计数键:ratelimit:{clientIP}:{path}:{窗口起点}
窗口起点 = Unix/window*window 对齐(固定窗口语义)
TTL = window+5s(仅用于回收,不影响窗口归属)
计数实现:incrCounter——GET→SET 自增;不存在时 SET NX 抢占;
并发下 NX 失败再读现值加一
计数存储:utility/cache(Redis 优先、内存降级)→ 多实例共享计数
超限响应:code=429 信封(CodeTooManyRequests)
存储异常:放行(限流不阻断业务)真实 IP(防绕过)
text
util.go realClientIP:
直连来源不在 api.proxyIps(逗号分隔,精确 IP 与 CIDR)内
→ 直接用 GetRemoteIp()(不信任任何转发头)
在列表内 → 才信任 X-Forwarded-For 首段 / X-Real-IP
目的:防止伪造 XFF 绕过限流或污染日志
部署:Nginx 反代务必把反代网段加入 api.proxyIps,否则全站记反代 IP固定窗口的语义与边界
text
固定窗口特点:窗口边界处可能出现 2 倍瞬时放行(如 59s 放 120 + 61s 放 120)
本项目取舍:实现简单、Redis 计数成本低;对管理后台场景足够
(非支付级防刷场景,无需滑动窗口/令牌桶)
限流粒度:IP + 路径 —— 同 IP 调不同路径各自计数与整体防护的关系
text
接口限流(本节) IP 维度,防刷与暴力破解
+ 认证专项 authLimit 独立严阈值(验证码/登录/令牌)
+ 演示模式 demo=true 拦截全部写操作
+ 权限校验 403 拒绝未授权
+ jti 黑名单 已撤销令牌即时失效调参与排障
| 场景 | 处理 |
|---|---|
| 正常用户被误伤(429) | 压测/办公出口 IP 共享场景调大 limit;authLimit 不建议调大 |
| 限流不生效 | enable=false?内存降级下单机计数仍应生效;确认路径带 /api 前缀 |
| 多实例阈值翻倍 | Redis 不可用内存降级(各实例各自计数)——查 /api/cache/info |
| 反代后全站共享阈值 | api.proxyIps 未含反代出口(全站记反代 IP,共同一个桶) |
| 突发 429 后恢复慢 | 固定窗口未到窗口起点;键 TTL=window+5s 保证自动回收 |
生产建议
- authLimit(认证入口 10 次/分钟/IP)是防暴力破解的关键阈值,不建议调大
- 限流计数依赖 Redis——内存降级时多实例部署下实际阈值 = 配置值 × 实例数
- 压测前临时调大 limit 或按压测 IP 加白(受信代理配置不等同限流白名单,需另行处理)