verifyreq 不能直接用于生产环境,因其仅校验验证码值而无ip限频、单次消费、过期控制等防护;需结合redis原子操作、ip请求计数、前端防抖及正确ip提取,构建全链路防刷机制。

Beego 默认的 captcha 模块不带冷却时间与防刷逻辑,直接用 VerifyReq 校验会暴露两个致命问题:验证码可无限刷新、同一 IP 可暴力重试。必须自己补上 IP 限频 + 验证码单次消费 + 过期控制。
为什么 VerifyReq 不能直接用于生产环境
Beego 的 captcha.VerifyReq 只做两件事:从请求里取 captchaId 和用户输入的值,再查缓存比对。它不关心这个 ID 是不是刚被用过、是不是同一个 IP 在 1 秒内刷了 20 次、缓存有没有被清空后又重建——这些全靠你兜底。
- 内存缓存(
NewMemoryCache)重启即丢,集群下完全不可用 -
VerifyReq成功后不会自动删缓存,攻击者拿到一个有效captchaId就能反复提交 - 没有 IP 维度的请求计数,穷举 4 位数字只要最多 10000 次,几秒就爆破完
用 Redis 替换内存缓存并强制单次消费
把 captcha 的 store 换成 Redis 实例,并在验证时用 GETDEL(Redis 6.2+)或原子化 GET + DEL 确保验证码只能用一次。
- 键名格式统一为
captcha:{uuid},避免和其他业务 key 冲突 - 写入时用
SETEX captcha:{uuid} 300 "{\"code\":\"ABCD\",\"created_at\":1717023456}\",靠 Redis 自身 TTL 控制 5 分钟过期 - 校验时先
GETDEL captcha:{uuid},如果返回 nil 说明已用过或超时,直接拒绝 - 别在 Go 里用
time.Now().Add(5 * time.Minute)算过期时间再塞进 Redis,时钟不同步会导致提前失效
给每个 IP 加 60 秒内最多 5 次验证码请求限制
在调用 captcha.VerifyReq 前,先检查该客户端 IP 的请求频率,超限就跳过验证码逻辑直接返回错误。
- IP 提取用
c.Ctx.Request.RemoteAddr,但要注意反向代理场景,得优先读X-Forwarded-For头 - Redis key 设为
rate:ip:{client_ip},用INCR+EXPIRE原子组合实现计数器 - 若
INCR返回值 > 5,立刻c.Abort("429")并记录日志,不走后续验证码流程 - 不要等校验失败后再限频——失败本身就是攻击信号,得在生成和校验两个入口都设防
前端刷新按钮必须带时间戳且禁用重复点击
即使后端做了所有防护,前端不配合也会让防刷形同虚设。关键点不在“怎么生成图”,而在“怎么不让用户乱点”。
-
<img src="/captcha?t=%7B%7B.Timestamp%7D%7D" onclick="this.src='/captcha?t='+Date.now()">,强制每次刷新都带新时间戳防缓存 - 点击刷新后立即
this.disabled = true,3 秒内禁止再次点击(防止手抖连点) - 验证码图片加载完成前,登录按钮应置灰;加载失败时提示“获取验证码失败,请重试”而非静默忽略
- 不要把
captchaId存在前端 JS 变量里传给后端——它应该只出现在表单隐藏域或请求 URL path 中,由 Beego 框架自动注入
真正难的不是写完这四段逻辑,而是把 IP 限频、Redis 原子操作、前端防抖、错误响应状态码全部串成一条无漏洞链路。任意一环松动,比如忘了在 GETDEL 后加 created_at 时间戳校验,或者没处理好 Nginx 转发后的 IP 头,攻击者就能绕过去。











