api防护需分层拦截、精准识别、快速响应:redis毫秒级限流须按ip+路径组合并原子操作;高危接口叠加user_id/device_id;签名须字典序拼接、timestamp≤30秒、nonce防复用;验证码仅用于高危操作且服务端校验后立即销毁;日志监控需记录限流详情并设关键词告警。

API 接口被刷,不是“会不会发生”的问题,而是“什么时候开始、刷到什么程度才崩”的问题。单纯靠加个 if ($ip_count > 100) 拦不住真实攻击,也拦不住合法用户多端并发——真正有效的防护,是分层拦截 + 精准识别 + 快速响应。
用 Redis 实现毫秒级 IP 限流,但别只 count++
现象:刚加完 $redis->incr($ip),发现攻击者换代理就绕过;或者内部系统调用也被误杀。
原因在于没做维度隔离和时间窗口对齐——IP 是最粗的粒度,必须叠加更多上下文。
- 按
IP + 接口路径组合限流,避免 /login 被刷影响 /public/info - 用
INCR + EXPIRE原子操作替代先 GET 再 SET,防止并发漏计数:$key = "rate:{$ip}:{$path}";$redis->eval("return redis.call('INCR', KEYS[1]) * redis.call('EXPIRE', KEYS[1], ARGV[1])", 1, $key, 60); - 对登录、短信等高风险接口,额外加
user_id或device_id维度,防账号爆破
签名验证不是加个 sha1($secret . $data) 就安全
现象:签名校验通过,但请求参数被重放或篡改;或者服务端验签失败,客户端却说“我明明按文档写的”。
立即学习“PHP免费学习笔记(深入)”;
问题出在签名构造规则不一致、时间窗太宽、nonce 复用没防控。
- 参数必须按字典序(ASCII)排序后拼接,不能靠
json_encode()—— 它不保证键序,且空格/换行不可控 -
timestamp有效期建议 ≤ 30 秒,超时直接拒收,避免重放攻击 -
nonce要存 Redis 并设 TTL(如 5 分钟),且必须用SETNX+EXPIRE原子写入,否则并发下可能漏判 - 密钥
$secret_key别硬编码,应从环境变量或配置中心加载,禁止提交到 Git
别让验证码成为性能瓶颈,也别让它形同虚设
现象:加了验证码后,接口 QPS 断崖下跌;或者验证码图片能被 OCR 自动识别,防护等于没做。
验证码本质是人机区分手段,不是所有接口都该上,更不该用错位置。
- 仅对高危操作启用:如登录、注册、密码重置、短信发送,不要放在通用数据查询接口上
- 服务端生成时,把答案存 Redis 并绑定
session_id或临时token,过期时间 ≤ 5 分钟 - 避免使用易识别的数字+字母组合(如
123abc),推荐带干扰线+轻微扭曲的图形验证码,或更现代的hCaptcha/reCAPTCHA v3(后端只验 token) - 前端提交验证码时,必须携带对应
captcha_token,服务端比对后立即DEL,杜绝重放
日志和监控不是上线后才配,而是限流逻辑的一部分
现象:被刷了才发现,查日志要翻半小时;或者告警阈值设成“CPU > 90%”,等收到邮件时服务已瘫痪。
防御失效的第一现场永远在日志里,但没人看的日志等于没记。
- 记录被限流的请求:IP、路径、
timestamp、触发的规则名(如ip_path_60s_10),写入独立access_rate.log - 用
Monolog配合syslog或ELK,设置关键词告警:“Too many requests”、“Invalid signature”、“Captcha mismatch” - Redis 中限流 key 的命中率、过期率、平均 TTL 剩余时间,都是关键指标——它们比服务器 CPU 更早暴露攻击模式
- 定期跑脚本清理长期未更新的
nonce和验证码缓存,避免 Redis 内存缓慢泄漏
真正难的不是写几行限流代码,而是判断哪个接口该用哪一层防护、每层之间怎么协同不打架、以及当某条规则突然开始高频触发时,你能不能 5 分钟内定位是业务变更还是攻击升级。这些细节不会出现在框架文档里,但决定了你的 API 是扛住百万请求,还是在凌晨三点被一个 Python 脚本干趴。











