验证码接口是少数"每次调用都要花资源"的接口(要发邮件),也是最容易被拿来刷的接口。限流必须做,但做法讲究。
分三层限流
- 单邮箱冷却:同一邮箱 60 秒内只能发一次。这是用户最能理解的一层,也是前端倒计时对应的规则。
- 单 IP 频次:同一来源 10 分钟内最多 8 次,防止一个脚本换着邮箱刷。
- 全局开关:发现异常时能一键关掉发信,避免把邮箱额度打光。
别只按 IP 限
公司、学校、手机运营商出口都是 NAT,一群人共用一个出口 IP。只按 IP 限会出现"别人刷爆了、我发不出去"的误伤。冷却按邮箱、频次按 IP,两层配合最稳。
冷却时间要"可反馈"
返回里带上还要等多少秒,前端就能把按钮变成倒计时,而不是让用户以为坏了:
if ($wait > 0) {
api_out(0, '发送太频繁,请 ' . $wait . ' 秒后再试', array('wait' => $wait));
}
先做人类验证,再发信
在"生成验证码 + 发邮件"之前插一道人机验证(图形验证码或行为验证),成本远低于发一封邮件。顺序很重要:验证 → 限流 → 发信。
错误信息别泄密
- 不要把 SMTP 原始应答、服务器路径回给前端
- 技术细节写进服务器日志,前台只给一句"请稍后重试"
最后
限流的目标不是"挡住所有人",而是"让正常用户几乎无感,让脚本的成本高到不值得"。宁可阈值宽一点、层数多一点。