没有控制的联系表单或公开 API,就像没有门卫的大楼:谁都能进,留下垃圾,或试图撬门。垃圾信息、暴力破解与抓取不是“小噪音”:它们塞满收件箱、耗尽邮件额度,还能在周五下午打垮一个接口。
本文解释分层防护——与本作品集在 Astro 生产环境中使用的模式相同,也适用于 Laravel 与 Node——让非技术读者理解原因,让技术人员获得检查清单、检查顺序与应避免的错误。
为什么对业务重要
每个虚假线索都消耗销售时间。每次无限制的登录尝试都是对泄露凭据的抽奖。每个“敞开”的 webhook 或接口都是滥用面。防护不是偏执:是让渠道对真人有用、对机器人昂贵。
- 可用的收件箱:更少垃圾 = 更多对真实客户的回复。
- 可用性:速率限制防止脚本拖垮服务器。
- 信任:服务端校验减少 CRM 垃圾与虚构工单。
- 务实合规:日志与数据库中更少攻击者 PII。
关键概念(通俗 + 技术)
分层门卫
任何单层都不够。蜜罐抓住笨机器人;速率限制抑制流量;计时检测非人类提交;校验与清洗守护进入内容;Turnstile 仅在需要时增加摩擦。像大楼:前台、摄像头、钥匙与警报——而不是只贴一张“禁止入内”。
| 层级 | 阻挡什么 | 典型响应 |
|---|---|---|
| 速率限制 | 流量 / 暴力破解 | 429 + Retry-After |
| 蜜罐 | 填满所有字段的机器人 | 静默 200(不处理) |
| 表单计时 | 提交 < ~3.5s | 400 |
| 来源检查 | 基础跨域脚本 | 403 |
| Turnstile | 更复杂的机器人 | 令牌无效时 400 |
| Zod + 清洗 | 垃圾与异常载荷 | 带字段错误的 400 |
速率限制
在时间窗口内按 IP(适用时也按用户)限制请求。单进程可用内存;多副本需要 Redis 或其他共享存储。参考示例:登录 5 次/分钟、重置密码 3 次/小时、联系 5 次/分钟。
蜜罐与计时
蜜罐是人类看不见的隐藏字段(display: none 或屏外)。若被填写,应静默拒绝(返回 200 但不处理),以免训练机器人。计时在渲染时存时间戳,并拒绝比合理人类更快的提交。
校验、清洗与来源
前端校验改善体验;服务端校验才是关键。使用模式(Zod 或等价物)、对纯文本去 HTML、限制最大长度,并在生产检查 Origin/Referer。并非万无一失,但能挡住大量廉价噪音。
实践指南:接口中的流程
- 速率限制检查 → 超限返回
429。 - 蜜罐检查 → 隐藏字段有值则静默
200。 - 表单计时检查 → 过快返回
400。 - 来源检查 → 来源非本站则
403。 - Turnstile(若已配置)→ 令牌失败则
400。 - 模式校验 → 带字段错误的
400。 - 清洗 → 再处理(邮件、CRM、队列)。
做好蜜罐
- 用 CSS 隐藏;能避免时不要用过于明显的
type="hidden"。 - 用迷惑性名称(
website、company_url)——不要叫honeypot。 tabindex="-1"与autocomplete="off";勿被屏幕阅读器意外触及。- 若已填写:返回虚假成功,不要明确
403。
Cloudflare Turnstile
当垃圾量需要额外摩擦时,Turnstile 更轻量,也比更激进的方案更尊重隐私。客户端用 TURNSTILE_SITE_KEY;TURNSTILE_SECRET_KEY 仅在服务端。服务端在处理之前验证令牌。
常见错误
- 只信任前端校验。
- 只在登录做速率限制,忽略联系与其他公开接口。
- 蜜罐可见、有真实标签,或可被键盘/阅读器聚焦。
- 对蜜罐返回
403(机器人会学习并适应)。 - 记录含邮箱与电话的完整载荷(日志中的 PII)。
- 三副本各自内存计数(真实限额被放大)。
- 错误信息泄露某邮箱是否“存在于系统”。
防护检查清单
- 列出敏感公开接口(登录、联系、重置、webhook)。
- 速率限制有文档化阈值与
Retry-After。 - HTML 表单具备蜜罐 + 计时。
- 生产环境检查 Origin/Referer。
- 服务端校验模式 + 清洗。
- 垃圾严重时可选 Turnstile。
- 多副本时使用共享存储(Redis)。
- 日志避免不必要 PII;用 429/400 指标发现攻击。