浏览器端生成验证码无安全意义,必须服务端生成并比对;推荐session存随机字符串、前端data属性显示、提交时服务端严格校验并立即清除。

为什么不用 JavaScript 生成验证码图片
浏览器端生成的图片验证码(比如用 canvas 绘制)毫无安全意义——源码全在前端,机器人直接读像素或绕过校验就行。真要防自动化提交,必须服务端参与生成和比对。
简单表单验证码的核心不是“看起来像验证码”,而是“提交时能和服务端存的值对上”。所以优先走服务端 session + 纯文本方案,够用、轻量、不依赖图像库。
- 后端生成随机字符串(如 4~6 位字母+数字),存进
session或内存缓存(如redis),键为用户会话 ID - 前端显示该字符串(可加简单干扰线 CSS,但非必需)
- 表单提交时,把用户输入的值和
session中存的原始值比对 - 比对完立即清空该
session键,防止重放
HTML 表单里怎么嵌入动态验证码文本
不能写死 <input type="text" value="AB3X">,得让后端吐出真实值。最简做法:后端渲染 HTML 时,把验证码明文塞进一个带 data-* 属性的容器里,再用 JS 读出来显示——既避免裸露在 DOM 文本中被爬虫扫到,又不用额外接口。
示例(假设后端模板语法是 Jinja2 / EJS 类):
立即学习“前端免费学习笔记(深入)”;
<div id="captcha-display" data-value="{{ captcha_text }}"></div>
<input type="text" name="captcha" required>对应前端 JS(无需框架):
采用zblog修改的模板,简单方便,直接解压上传到空间即可使用,页面简单,适合SEO,导航,次导航,最新文章列表,随机文章列表全部都有,网站采用扁平结构,非常适用淘宝客类小站,所有文章都在根目录下。所有需要修改的地方在网页上各个地方都有标注说明,一切在网站后台都可以修改,无须修改任何程序代码,是新手的不二选择。后台登陆地址: 域名/login.asp用户名:admin (建议不要修改)密码:adm
const el = document.getElementById('captcha-display');
if (el) {
el.textContent = el.dataset.value || '';
}- 不要把
captcha_text放在<input value="...">里——容易被自动填充插件覆盖 - 不要用
innerHTML渲染,避免 XSS;textContent更安全 - 如果服务端没传
data-value,前端留空或显示“加载失败”,别 fallback 到固定字符串
提交时如何验证用户输入是否匹配
关键不在前端校验(那只是体验优化),而在于后端收到 POST 请求后,立刻查当前会话对应的验证码值,并严格比对(区分大小写、去首尾空格)。
常见错误:
- 前端 JS 校验通过就放行——完全无效,绕过 JS 一抓一个准
- 后端比对用
==而非===,导致"0000"和0意外相等 - 没清空
session.captcha,导致同一验证码可重复使用多次 - 没设超时(比如 5 分钟),旧验证码长期有效,增大撞库风险
推荐后端逻辑(伪代码):
if (!session.captcha || Date.now() - session.captcha.ts > 5 * 60 * 1000) {
return error("验证码已过期");
}
if (userInput.trim() !== session.captcha.text) {
return error("验证码错误");
}
delete session.captcha; // 必须删要不要加倒计时刷新按钮
要,但别用复杂轮询。用户看不清时,点一下刷新,后端重新生成并返回新值 + 新 session 键,前端只更新 data-value 和显示文本即可。
实现要点:
- 刷新按钮绑定
fetch("/captcha/refresh"),返回 JSON:{"text": "K9mP"} - 成功后更新
data-value和textContent,不重载页面 - 按钮禁用 1 秒防连点,避免并发请求污染 session
- 别在前端记倒计时——时间以服务端为准,否则时钟不同步会导致提前失效
真正难处理的是并发刷新 + 提交竞争:用户狂点刷新,又立刻提交,可能拿着旧值去比对。解决办法只有一个——每次刷新都生成新 key,提交时校验必须用本次刷新生成的那个值,而不是“最新一次”的值。









