真正起效的验证码必须服务端生成并校验,前端仅负责展示、输入和提交;每次换码需新请求获取唯一captcha_id,后端用redis严格比对并立即失效,响应不区分错误类型。

HTML5表单里加验证码,真防不了刷
纯前端生成、校验的 HTML5 表单验证码,本质上是公开的——攻击者直接读取 JS 逻辑、绕过渲染、调用 generateCode() 函数就能批量获取码值。它只对非恶意用户(比如手抖点快的正常人)有点干扰作用。
真正起效的前提只有一个:验证码的生成、比对逻辑必须落在服务端,前端只负责展示和提交。
前端能做的只有“不让用户漏填”和“加点干扰”
前端唯一靠谱的职责是:确保用户看清、输入、传过去;同时让自动化脚本多花几毫秒解析 DOM 或图像。别指望它挡 bots。
- 用
<canvas></canvas>渲染字符比用纯文本<span></span>难一点 OCR,但依然可破解 - 避免把验证码明文塞进
data-code属性或全局变量,比如window.codeValue - 每次点击“换一换”必须触发新请求(
fetch('/api/captcha')),不能只重绘本地缓存 - 输入框加
autocomplete="off"和autocapitalize="none",减少浏览器自动填充干扰判断
后端不校验,前端所有努力都白搭
常见错误是前端生成了 captcha.png,也写了 checkCaptcha(),但这个函数只在浏览器里跑,没发请求到后端。结果攻击者跳过 JS,直接 POST 表单 + 任意字符串,照样进接口。
立即学习“前端免费学习笔记(深入)”;
- 表单提交必须携带一次性
captcha_id(比如 UUID)和用户输入的captcha_text - 后端收到后,查缓存(如 Redis)中该
captcha_id对应的真实值,严格比对、立即失效 - 校验失败返回统一错误:
{"code":400,"msg":"验证码错误或已过期"},不区分“错”和“过期”,防止枚举 - 不要在响应头或 HTML 中泄露服务端校验逻辑,比如
X-Captcha-Debug: true
简单实现里最容易被忽略的细节
不是“能显示+能提交”就完事。一个真实可用的轻量级方案,三个点卡住多数低阶爬虫:
- 验证码图片的
src必须带时间戳参数(如?t=1712345678901),否则浏览器可能复用缓存,导致多次提交用同一个码 -
input的name值别写成固定字符串如captcha,建议拼接随机前缀,比如captcha_abc123,增加解析成本 - 后端生成的图片响应头必须设
Cache-Control: no-store, no-cache,禁用中间代理缓存
复杂点从来不在“怎么画字”,而在“怎么让每个码只活一次、只认一次、且不被猜中”。其他都是障眼法。











