Canvas图形验证码不能用于真实登录注册场景,因其由前端生成且明文可查,无服务端校验,OCR识别率高,且缺乏session关联,完全不具备安全防护能力。

纯前端用 canvas 画出来的图形验证码,**不能用于真实登录、注册等需防机器提交的场景**。它不经过服务端生成和校验,等于把答案写在网页源码里,绕过只要右键“查看网页源码”或重放一次 draw() 就能拿到明文。
为什么 canvas 验证码在生产环境基本等于没设防
浏览器端 JS 调用 Math.random() 生成字符串、用 ctx.fillText() 写到 canvas 上,整个过程用户完全可见可调试:
- 打开 DevTools → Sources → 找到
generateRandomString()函数,一眼看到字符集和长度 - 打断点进
drawText(),text参数就是当前验证码明文 - 哪怕加了噪点、扭曲、旋转,OCR 工具(如 tesseract)对这种规则排布+高对比度的图识别率仍超 90%
- 没有 session 或 token 关联,后端根本无从比对——你提交的值,后端连“该信谁”都不知道
如果你只是练手或做本地演示,canvas 可以这么写
目标:点击 canvas 刷新一个 4 位字母数字组合,不追求防 OCR,只跑通绘制逻辑。
- HTML 中确保
canvas有明确id和尺寸:<canvas id="captcha" width="120" height="40"></canvas> - JS 里用
secrets不现实(浏览器没这个模块),改用更难预测的字符池:const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' - 每个字符单独绘制,并加轻微随机偏移避免整齐排列:
ctx.fillText(char, x + Math.random() * 6 - 3, y + Math.random() * 4 - 2) - 手动关掉字体抗锯齿:
ctx.textRendering = 'optimizeSpeed',让文字边缘毛糙点 - 加 50~100 个噪点:
ctx.fillRect(Math.random() * 120, Math.random() * 40, 1, 1)
关键不是“画得多好看”,而是让同一段 JS 多次执行时输出视觉差异足够大——否则缓存或截图比对就能批量过。
立即学习“前端免费学习笔记(深入)”;
真正要上线,必须走服务端生成 + session 校验
前端唯一该做的事:正确请求图片、防止缓存、安全提交。其余全是后端责任。
-
img的src必须带时间戳或随机参数:/captcha?r=<code>Date.now()</code>,否则 Nginx/CDN 会返回旧图 - 后端接口(如
/captcha)响应头必须含:Content-Type: image/png,且禁用公共缓存:Cache-Control: no-store - session 存值别用
req.session.captcha这种泛名,优先用带前缀的键:req.session.auth_captcha_code,避免和其他中间件冲突 - 校验时先比对,再清空:
if (input.toLowerCase() === session.captcha_code) { delete session.captcha_code },否则重放攻击可行 - 别信“前端加密传 hash”——没服务端参与的任何 hash 都是障眼法,源头字符串还在内存里
最易被忽略的一点:开发时 localhost 跑得通,一上生产就报“验证码错误”,八成是反向代理(Nginx)没透传 cookie,或前后端域名不一致导致 session ID 无法携带。别急着改 canvas,先抓包看请求里有没有 Cookie: connect.sid=xxx。



















