秒杀系统高并发优化实战(C++ / Drogon):3.9 验证码接口安全加固:滑块验证码(设计篇,代码推迟到前端阶段)
⚠️ 本文是设计篇,不落地代码。 滑块验证码的完整实现(前端滑块 + 后端校验)推迟到「开始有前端」阶段再做。本篇只把后端必须自写的部分设计清楚、把契约定下来,避免到时候返工。原因见第六节「为什么代码推迟」。
3.3~3.5 做的是短信验证码(防脚本批量注册)。短信验证码有个软肋:它是"知识因子"——6 位数字,真能拦住人肉,但拦不住"养一批号 + 接码平台"的专业黄牛。滑块验证码补的是另一层:行为因子——你拖动滑块的轨迹,机器人很难逼真模仿。这一篇讲它的设计。
一、为什么是滑块:人机区分的本质
短信验证码回答"你有没有这个手机号",滑块回答"你是不是真人在操作"。两者维度不同:
- 短信 = 拥有因子(你有这个号,接得到码)
- 滑块 = 行为因子(你的拖动轨迹像人)
黄牛可以批量为号接码,但很难批量为每个号生成"像人"的拖动轨迹。所以滑块是挡自动化脚本的更前沿一道,通常放在"发短信/登录/下单"之前做前置人机校验。
二、前端有成熟框架,后端没有
前端滑块不要自己造轮子——GitHub 上有成熟开源实现:
| 框架 | 特点 |
|---|---|
vue3-slide-verify |
Vue3 滑块,交互顺滑,社区活跃 |
slider-verify-v3 |
轻量,Vue3 适用 |
AJ-Captcha |
行为验证码(滑块/点选),自带前后端契约,Java 生态成熟,C++ 后端需自写校验侧 |
但 C++ 后端没有现成实现——前端的"拼图/滑块组件"只是交互,真正的校验逻辑(challenge 生成、答案存储、容差、轨迹行为分析)必须后端自写,否则前端传来的"验证通过"标志可以被脚本直接伪造。
三、后端必须自写的部分
不管前端用哪个框架,后端要兜底四件事:
- challenge 生成:返回一张带缺口的背景图 + 缺口位置(或一段 challenge token),前端据此渲染滑块。答案(缺口 x 坐标)只在后端,前端永远拿不到真答案。
- 答案存 Redis:
captcha:challenge:{cid} → {answerX, expire, tryCount},有效期短(如 2 分钟),一次性消费(同短信验证码思路)。 - 容差:用户提交的
x与真答案差 ≤ 阈值(如 ±5px)即算通过——滑块本就是"大致对齐",不能要精确像素。 - 轨迹行为分析:前端上报拖动过程的
track(每个时间点的 x/y/时间),后端判断:① 速度是否过于匀速(机器人常匀速);② 是否有"先快后慢"的人手特征;③ 总时长是否合理(太短=脚本秒过)。这是滑块相比纯"对坐标"多出来的抗脚本层。
四、后端契约设计
定两套接口,前端框架照此对接:
1 | GET /api/captcha/challenge → 返回 {cid, bgImg, sliderImg, token} |
校验侧与短信同理,必须原子:比对 + 消费进一个 Lua(防并发同 challenge 被用两次)。通过后把 cid 标记为"已验证",注册/登录/下单流程在拿业务 token 前先校验这个 cid 已通过——等价于把滑块变成"前置人机闸门"。
1 | -- 校验脚本(KEYS: challengeKey;ARGV: submitX/maxDiff/maxTry) |
五、安全模型:轨迹分析 + 容差,抗脚本
| 维度 | 纯坐标比对 | 坐标 + 轨迹分析(本文) |
|---|---|---|
| 抗"直接发对坐标" | 弱(脚本可抓包重放) | 中(需真滑块交互) |
| 抗"模拟坐标" | 无 | 强(轨迹不像人被拒) |
| 容差 | 需精确 | ±阈值,贴合人手 |
注意:滑块验证码不是银弹。它把"自动化脚本"成本抬高到需要真模拟轨迹/养号,但对抗"人工打码平台"仍有限。正确定位是多层防护中的行为层,与短信验证码、登录锁定(3.6)、应用层在途闸门(4.8)叠加,而非单独扛全部防刷。
六、为什么代码推迟到前端阶段
项目决策(2026-09-02,见 README「项目边界:前端页面」):
- 验收硬指标零贡献:阶段一验收是"压测 QPS 提升一个量级 + 不超卖不重复下单",前端/滑块对这条指标无贡献。
- 分散主线:阶段一主线是"事务化 DB 直扣 → 缓存 → MQ → Lua 原子"的吞吐演进,重前端会拖主线。
- 引入成本:Vue/Vite 会把"两条 cmake 命令"变成"先装 Node 再 npm i",读者直接劝退;且当前
/api/seckill走 body 双通道、不校验 token,滑块的前置闸门没有挂载点。
已定的落地形态(到时候直接照做):单文件 www/index.html 原生 JS、无框架无 CDN;config.json 加 document_root: "./www" 走 Drogon 静态服务(同源零 CORS);前端引 vue3-slide-verify 等成熟框架,后端校验(challenge/Redis/Lua/轨迹)自写,挂载到 3.x 路由与 4.1/4.2 商品接口、/api/seckill 接 Authorization: Bearer。
功能抉择(本篇核心权衡)
① 为什么前端用成熟框架、后端自写校验?
滑块的"交互组件"是前端成熟领域,重复造轮子既丑又易出交互 bug;但"答案生成 + 校验"是安全边界,绝不能完全信任前端传来的"通过"标志——后端必须掌握真答案与校验逻辑,否则滑块形同虚设。
② 为什么校验也要 Lua 原子 + 一次性消费?
与短信验证码(3.5)同一理由:并发下同一 challenge 被用两次、试错不计数等竞态,只有 Lua 单线程原子能根除。
③ 为什么现在不写、留到前端阶段?
滑块的挂载点(/api/seckill 接 Bearer、前端页面)当前都不存在;现在写后端校验没有前端联调、也没有压测收益,纯属透支注意力。把契约(本节四)定清楚,等前端阶段一次落地,反而更省。
小结
- 滑块验证码补的是行为因子层,与短信(拥有因子)正交,专门挡自动化脚本。
- 前端用成熟框架(vue3-slide-verify / slider-verify-v3 / AJ-Captcha),后端校验必须自写:challenge 生成 + Redis 存答案 + 容差 + 轨迹行为分析。
- 后端契约:
GET /api/captcha/challenge+POST /api/captcha/verify,校验 Lua 原子 + 一次性消费,通过即前置人机闸门。 - 本文只设计、不落地代码——实现推迟到「开始有前端」阶段,落地形态已定(原生 JS 单页 + Drogon 静态服务 + 后端校验自写)。🐾

