验证码必须由后端生成并绑定会话,前端仅负责展示和提交;后端需返回captcha_id与图片,将答案存入Redis/session,校验后立即失效,防止重放。

验证码生成不能只靠前端 JavaScript
纯前端生成的 Math.random() 验证码毫无安全意义——用户能直接读取、绕过、批量提交。真正有效的验证码必须由服务端生成并绑定会话(如 session_id 或 token),前端只负责展示和提交。常见错误是把验证码字符串写死在 HTML 里,或用 JS 拼接后藏在隐藏字段中,这等于没设防。
推荐做法:后端提供一个独立接口(如 /api/captcha),返回两样东西:captcha_id(唯一标识)和图片 Base64 或临时 URL;同时将对应答案存入 Redis 或内存 session,有效期建议 5 分钟。
前端表单需同步提交 captcha_id 和用户输入
用户填写完验证码后,表单提交时必须带上两个关键字段:captcha_id 和 captcha_input。漏掉 captcha_id 就无法查到服务端存储的答案,校验必然失败。
常见疏忽:
立即学习“前端免费学习笔记(深入)”;
- 用
input type="hidden"存captcha_id,但没在刷新验证码时更新它的值 - 用户点击“换一张”后,只换了图片,没重置
captcha_id字段,导致新图和旧 ID 不匹配 - 表单用
fetch提交但没加credentials: 'include',导致 session 丢失,后端查不到该 ID 对应的验证码
后端校验必须严格比对 + 即时失效
收到表单后,后端要按顺序执行三步:查存在 → 比对值 → 立即删除。缺一不可。
为什么必须删除?因为同一个 captcha_id 若允许多次验证,攻击者可重放请求。为什么先查再删?避免因网络重试造成“查到但删失败”,留下悬空验证码。
典型错误逻辑:
if (storedValue === userInput) {
// ✅ 正确:校验通过后立刻 del(captcha_id)
redis.del(captcha_id);
return success();
} else {
// ❌ 错误:不删,下次还能试
return error();
}
简单图形验证码够用时别上 hCaptcha
如果只是防止低频脚本注册或留言,一个 4 位字母+数字、带轻微扭曲和噪点的 PNG 图片(服务端用 PIL(Python)或 canvas(Node.js)生成)完全够用,开发成本低、兼容性好、无第三方依赖。
只有当面临真实 OCR 攻击或合规要求(如金融类)时,才考虑接入 hCaptcha 或 reCAPTCHA v3。它们需要额外域名配置、HTTPS、前端加载 SDK,且 v3 不显示 UI,容易让开发者误以为“没做任何事就过了”,其实它依赖行为分析,对内网或隐私浏览器支持差。
最常被忽略的一点:图形验证码的字体文件路径、临时图片目录权限、GD 库是否启用——这些环境细节出问题时,页面只显示一个破损图标,但控制台毫无报错。



















