不知大家网站评论区是否经常收到一些垃圾信息,明明已经添加了验证码,还是莫名其妙的被各种机器刷爆了评论区。于是经过半个月的苦心钻研,我开发了一款汉字点选验证码插件,它用 PHP GD 库生成、纯 jQuery前端交互,不依赖任何第三方服务。我会从攻防视角出发,对比它与传统字符验证码、滑块验证码的优劣,并拆解它背后的安全设计。

最经典的验证码——把数字和字母画成扭曲、加干扰线、加噪点的图片,让用户输入。
它的问题是根本上的:字符验证码的"答案"就写在图片里,机器只要"认字"即可。深度学习模型(如 ddddocr)对常见字符验证码的识别率已经超过 99%,而且开箱即用,攻击者一行代码就能接入。开发者越扭曲字符,人越难认,但对神经网络几乎不构成障碍。
这是一场注定失败的军备竞赛。
滑块验证码要求用户拖动拼图块到缺口处。它的思路是"不考知识,考行为"——通过拖动轨迹判断是否人类。
但它有两个致命弱点:
缺口位置可被图像算法精确求出。主流做法是对背景图做边缘检测/模板匹配,或直接用 ddddocr 的目标检测模型定位缺口,误差通常在 1~2 像素内。
拖动轨迹可以被模拟。把"人类滑动曲线"建模成带加速度的贝塞尔曲线,或直接录制真人轨迹回放,服务端的轨迹校验很容易被绕过。
我实测过用 ddddocr + Puppeteer 自动过滑块,从识别缺口到拖完提交,整个流程不到 1 秒,成功率接近 100%。一旦攻击者有了这套脚本,滑块验证码就形同虚设。
更糟糕的是,滑块验证码答案唯一——缺口坐标是固定的,机器只要算出一个数就够了。
点选验证码的做法是:在一张图上随机放几个汉字,告诉用户"请按顺序点击:春、江、花、月",用户需要:
1.识别出每个目标字在图上的位置(认字 + 定位)
2.按顺序依次点击
3.点击坐标在服务端预设的容差范围内才算通过
它的核心优势是:机器既要"认字",又要"找到字在哪",还要"记住顺序"。这比字符验证码多了空间定位和序列记忆两个维度,比滑块多了内容理解这个维度。
生成汉字时,我没有用纯色填充,而是做了双色纵向渐变 + 颗粒纹理抖动:
每个字用两种随机亮色做纵向渐变填充
每个像素再叠加 ±7 的随机亮度抖动(颗粒纹理)
字号、倾斜角度(±22°)随机
渐变和颗粒让像素直方图分布变平,模板匹配的相关度峰值被削弱;旋转破坏了字形的规整性,OCR 需要先做文字方向校正才能识别。
同时画了 2 条亮色直线随机穿过某个字的中心,切断字形笔画的连续性,干扰目标检测模型对字符的分割。
滑块的缺口能用"模板图 - 背景图"求差来定位。点选验证码如果直接用固定背景图,攻击者拿到原图后也能做背景扣除,把字抠出来。
对策:每次生成都对背景图做 cover 裁剪 + 随机放大取景(65%~100%)+ 随机水平翻转。同一张背景图,每次出现的是不同的子区域、不同的缩放、可能还镜像翻转,原图和验证码图无法像素对齐,背景减法失效。
这也是为什么背景图目录建议放在 webroot 之外——不让攻击者直接拿到原图。
前端收到的只有:
验证码图片 base64
要点击的文字顺序(如 ["春","江","花","月"])
答案坐标只存在服务端 session 里,前端提交点击坐标后由服务端做圆形容差校验。攻击者无法从接口返回中拿到正确答案。
验证通过后服务端签发一个一次性 pass_token,业务接口(如登录、评论)凭此 token 放行,consumePassToken() 验过即删。
token 用 random_bytes(或 openssl CSPRNG 兜底)生成,不可预测
一次性使用,无法重放
绑定 session,跨会话无效
这防止了"脚本拿到一次验证结果反复提交业务请求"的攻击。
生成接口:15 次/分钟
校验接口:10 次/分钟
同一 IP 连续失败 5 次后锁定 5 分钟(含倒计时)
滑动窗口限流存在临时文件里,失败计数也按 IP 维度持久化。限流是验证码的最后一道防线——即使识别算法很强,也让爆破成本指数级上升。
服务端不只看坐标对不对,还看用户怎么点的:
耗时:从图片生成到提交,必须在 0.8~60 秒之间。太快(< 0.8s)判定为机器人,太慢说明过期。
鼠标轨迹:前端采集从打开弹窗到点完 4 个字的鼠标移动轨迹,服务端校验:
轨迹点数 ≥ 20
平均速度 > 0.01
速度方差不能太小(不能匀速)
路径总长度 / 直线距离 > 1.3(不能走直线)
移动时间间隔方差不能太小(不能等间隔)
这些条件组合起来,让"算好坐标直接伪造点击"的脚本失效——它还需要模拟一条像人的鼠标轨迹。
每次生成验证码随图返回一个 CSRF token,提交时必须原样带回,与 session 中存储的比对。防止跨站请求伪造。
图片生成耗时约 32ms(PHP 8 环境),图片用 JPEG 质量 85 编码,单张约 22KB,加载快,兼容 PHP 5.6 32位 到 PHP 8.x(imageflip 有手动列镜像兜底,imageline 坐标做了 (int) 强转)
前端依赖 jQuery 1.7+,无其他依赖
当然任何验证码都有被攻破的可能,点选验证码也不例外:
图片生成后,字的位置对机器是可学习的。如果攻击者专门针对你的字库和渲染风格训练目标检测模型,识别率会逐步上升。对策是定期更新字库和背景图库、调整渲染参数(角度范围、干扰线数量等)。
行为校验有一定误伤率。网络慢、操作慢的用户可能触发"耗时过长"或"轨迹点不足"。可以通过调高 maxCostSec 和放宽轨迹阈值来平衡。
它不能替代风控。验证码是第一道闸门,登录风控、IP 信誉、设备指纹等手段需要配合使用。
该验证码演示地址:
https://blog.yzmcms.com/php/311.html 或 YzmCMS演示站 均已上线,后续升级无影响。