最新文章
前端实现图形验证码:生成、展示与登录表单校验
前言
在 Web 登录场景中,验证码是常见的防护手段,可以有效防止机器批量暴力破解、脚本恶意提交表单。很多小伙伴会疑惑:验证码是不是只能后端生成?其实前端也可以实现简易验证码,在浏览器内生成随机验证码图片,登录提交前先在前端校验验证码,校验通过才允许提交表单。
⚠️重要提醒:前端验证码仅做第一层体验拦截,不能作为安全防线。前端代码可以被绕过,正式项目必须在服务端再次校验验证码,否则会被脚本直接跳过前端逻辑发起请求。本文演示纯浏览器端实现,用于学习交互逻辑。
实现思路
使用
<canvas>画布动态绘制验证码图片;随机生成 4 位验证码字符(数字 + 大小写字母);
画布增加干扰线、干扰点、随机字体颜色、旋转角度,增加识别门槛;
点击验证码图片可以刷新生成新验证码;
登录表单提交事件拦截,获取用户输入的验证码,和内存保存的正确验证码做对比;
验证码不一致:提示错误,阻止表单提交;验证码一致:放行,提交表单。
完整代码示例
完整代码示例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>前端验证码登录示例</title> <style> .login-box { width: 360px; margin: 80px auto; padding: 24px; border: 1px solid #eee; border-radius: 8px; } .item { margin-bottom:16px; } label{ display: block; margin-bottom:4px; } input{ width:100%; box-sizing: border-box; padding:8px; border:1px solid #ccc; border-radius:4px; } .code-wrap{ display:flex; gap:10px; } .code-wrap input{ flex:1; } canvas{ cursor:pointer; border:1px solid #ccc; border-radius:4px; } button{ width:100%; padding:10px; background:#2b7bdd; color:#fff; border:none; border-radius:4px; cursor:pointer; } .tip{ color:red; font-size:13px; height:18px; } </style> </head> <body> <div> <form id="loginForm"> <div> <label>账号</label> <input type="text" name="username" placeholder="请输入账号"> </div> <div> <label>密码</label> <input type="password" name="password" placeholder="请输入密码"> </div> <div> <label>验证码</label> <div> <input type="text" id="codeInput" placeholder="输入验证码"> <canvas id="codeCanvas" width="120" height="40"></canvas> </div> </div> <div id="errorTip"></div> <button type="submit">登录</button> </form> </div> <script> // 全局保存正确的验证码 let validCode = ''; const canvas = document.getElementById('codeCanvas'); const ctx = canvas.getContext('2d'); const codeInput = document.getElementById('codeInput'); const errorTip = document.getElementById('errorTip'); const loginForm = document.getElementById('loginForm'); // 生成随机字符库:数字+大小写字母 function getRandomChar(){ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; return chars[Math.floor(Math.random() * chars.length)]; } // 生成随机颜色 function getRandomColor(){ const r = Math.floor(Math.random()*150); const g = Math.floor(Math.random()*150); const b = Math.floor(Math.random()*150); return `rgb(${r},${g},${b})`; } // 绘制验证码 function drawCode(){ // 清空画布 ctx.clearRect(0,0,canvas.width,canvas.height); // 背景色 ctx.fillStyle = '#f6f6f6'; ctx.fillRect(0,0,canvas.width,canvas.height); validCode = ''; // 生成4位验证码 for(let i=0;i<4;i++){ const char = getRandomChar(); validCode += char; ctx.font = `bold ${24 + Math.random()*6}px Arial`; ctx.fillStyle = getRandomColor(); // 文字旋转 ctx.save(); const rotate = (Math.random()-0.5)*0.3; ctx.translate(25+i*25,28); ctx.rotate(rotate); ctx.fillText(char,0,0); ctx.restore(); } // 画干扰线 for(let i=0;i<4;i++){ ctx.strokeStyle = getRandomColor(); ctx.beginPath(); ctx.moveTo(Math.random()*canvas.width, Math.random()*canvas.height); ctx.lineTo(Math.random()*canvas.width, Math.random()*canvas.height); ctx.stroke(); } // 画干扰点 for(let i=0;i<40;i++){ ctx.fillStyle = getRandomColor(); ctx.beginPath(); ctx.arc(Math.random()*canvas.width,Math.random()*canvas.height,1,0,2*Math.PI); ctx.fill(); } } // 页面初始化绘制验证码 drawCode(); // 点击画布刷新验证码 canvas.addEventListener('click',()=>{ errorTip.innerText = ''; drawCode(); }) // 表单提交拦截 loginForm.addEventListener('submit',function(e){ e.preventDefault(); // 阻止表单原生提交 const userInput = codeInput.value.trim().toUpperCase(); const rightCode = validCode.toUpperCase(); // 判断验证码是否一致 if(!userInput){ errorTip.innerText = '请输入验证码'; return; } if(userInput !== rightCode){ errorTip.innerText = '验证码错误,请重新输入'; drawCode(); // 验证码错误刷新验证码 codeInput.value = ''; return; } // 验证码校验通过,执行业务提交 errorTip.innerText = ''; alert('验证码校验通过,准备提交登录表单'); // 这里写ajax/fetch提交账号密码逻辑 // fetch('/api/login',{method:'POST',body:xxx}) }) </script> </body> </html>核心逻辑讲解
1. 验证码生成
利用canvas绘图 API,循环取出随机字符,给每个字符设置随机大小、颜色、旋转角度;增加干扰线和噪点,防止简单脚本截图识别。变量validCode保存在 JS 内存中,存储本次正确验证码。点击 canvas 画布触发重新绘制,实现刷新。
2. 表单拦截与校验
给 form 绑定submit事件,调用e.preventDefault()阻止浏览器默认表单跳转。
拿到用户输入的验证码,统一转大写做对比,忽略大小写差异。
为空:提示输入验证码;
不一致:提示错误,刷新验证码,清空输入框;
一致:提示校验通过,执行登录接口请求。
存在的缺陷与生产环境建议
前端验证码缺点
验证码保存在前端 JS 变量,懂技术的人打开控制台可以直接读取
validCode拿到正确验证码,很容易被绕过;无法对抗爬虫脚本,脚本可以直接删掉 JS 校验逻辑,直接发送登录接口请求。
线上项目正确做法
验证码由后端生成:后端生成随机码,生成图片返回前端,同时把验证码存 session/redis;
用户登录提交时,把用户输入的验证码传给后端,后端做校验;
前端仅做交互提示,不做可信校验;
可以保留前端简单校验,优化用户体验,但不能替代服务端校验。
扩展优化方向
增加验证码长度可配置,比如 4 位 / 6 位;
增加输入框失去焦点实时校验,不用等到点登录才提示;
增加验证码过期逻辑,一段时间自动刷新;
换成滑动验证码、点选验证码提升防机器能力。
总结:前端验证码适合做用户体验优化,减少无效请求,安全校验必须交给后端,这是开发登录模块一定要注意的关键点。












冀公网安备