|
验证码这玩意儿,登录说白了就是验证服务器给客户端出的一道题,答对了才让进门。码代码HTML里做登录验证码,登录最直接的验证办法是用canvas画一张图,前端生成随机字符,码代码用户照着填。登录但纯前端验证码防不住脚本——攻击者直接读canvas的验证像素数据就能识别。所以实际项目里,码代码验证码必须由后端生成,登录前端只负责展示和提交。验证 先看一个后端生成验证码的码代码典型流程。以Node.js为例,登录用svg-captcha库,验证三行代码就能出图: const svgCaptcha = require('svg-captcha'); const captcha = svgCaptcha.create({ size: 4,码代码 noise: 2 }); // captcha.text 是答案,存session // captcha.data 是SVG字符串,直接返回给前端 前端拿到SVG后,用innerHTML塞进一个容器里就行。关键点在于:答案存在服务端的session里,用户提交表单时把输入的字符一起发过去比对。session的key用req.session.captcha存,比对完立即销毁,防止重放。 HTML结构可以很简单: <form action="/login" method="POST"> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <div id="captcha-box"></div> <input type="text" name="captcha" placeholder="验证码"> <button type="submit">登录</button> </form> 页面加载时发一个GET /captcha请求,服务端返回SVG,前端把它渲染到#captcha-box里。用户点击图片刷新?加个点击事件重新请求就行。这里有个细节:SVG是矢量图,缩放不糊,但有些老浏览器对SVG的innerHTML支持不好,换成img标签的src指向接口地址更稳。 纯前端canvas方案也不是不能用,适合内部工具或者演示。核心代码大概这样: const canvas = document.getElementById('captcha'); const ctx = canvas.getContext('2d'); const chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; let code = ''; for (let i = 0; i < 4; i++) { code += chars[Math.floor(Math.random() * chars.length)]; } ctx.font = '24px sans-serif'; ctx.fillStyle = '#333'; ctx.fillText(code, 10, 30); // 加干扰线 for (let i = 0; i < 5; i++) { ctx.strokeStyle = `rgba(${ Math.random()*255},${ Math.random()*255},${ Math.random()*255},0.5)`; ctx.beginPath(); ctx.moveTo(Math.random()*120, Math.random()*40); ctx.lineTo(Math.random()*120, Math.random()*40); ctx.stroke(); } 但说实话,这种前端验证码只能防君子。我见过一个真实案例:某小网站用前端canvas验证码,结果被人写了个油猴脚本,直接读canvas的toDataURL,用tesseract.js做OCR,识别率超过90%。所以但凡涉及登录、注册、短信接口,验证码必须走服务端。 还有一种方案是用第三方服务,比如Google reCAPTCHA。它不显示字符,而是让用户勾选“我不是机器人”,背后靠行为分析打分。接入也简单,前端放个<div class="g-recaptcha" data-sitekey="..."></div>,引入脚本,提交时带上g-recaptcha-response,服务端拿这个token去Google的接口验证。缺点是国内访问不稳定,而且对用户体验有影响——有些用户就是不想勾那个框。 自己实现的话,还要注意几个坑。验证码字符别用0和O、1和l,用户分不清。字体大小和干扰线数量要平衡,太干净容易被OCR,太乱用户自己也认不出。我试过noise: 3加上旋转角度,人眼识别率还行,但OCR就吃力很多。另外,验证码有效期别设太长,5分钟足够,过期就刷新。 最后提一句存储。session存验证码在单机没问题,但多台服务器就要用Redis。key用captcha:${ sessionId},设置TTL,比对时用GETDEL原子操作,避免并发问题。这套下来,一个能用的登录验证码就齐了。 |