不可靠。prepare()中内存计数因多实例、重启、竞态失效;须用redis等外部存储限流;验证码需cache一次性消费,禁存session;自定义绘制更安全可控。

beego.Controller.Prepare() 里做登录限速是否可靠
不可靠。直接在 Prepare() 里用内存计数(比如 map[string]int)做 IP 或账号维度的请求频次限制,会因多实例部署、进程重启、goroutine 竞态而完全失效。Beego 自身不提供分布式限流原语,Prepare() 只是请求生命周期的前置钩子,不是原子执行上下文。
真正可用的方案必须依赖外部存储:
- 用
beego.Cache接 Redis(推荐),设置带过期时间的 key,如login:ip:192.168.1.100,每次请求Incr()并检查阈值 - 避免用文件缓存(
cache.FileCache),并发写入易出错,且无法跨进程同步 - 注意 key 命名要带业务前缀和粒度标识(IP / 账号 / 组合),否则不同限流逻辑互相污染
验证码图片生成后,怎么确保只校验一次且不被重放
核心是「服务端状态一次性消费」。Beego 没有内置的防重放验证码机制,必须手动控制缓存生命周期和校验逻辑。
关键操作点:
- 生成验证码时,用
beego.Cache.Put()写入唯一 key(如captcha:abc123),value 是明文答案,过期时间设为 5 分钟,不能更长 - 用户提交时,先
beego.Cache.Get()获取答案并立即beego.Cache.Delete()—— 这步不能省,否则同一验证码可重复使用 - 若
Get()返回 nil,说明已过期或已被消费,直接拒绝,不要返回具体提示(避免泄露状态) - 前端禁止禁用验证码输入框后仍允许提交,但这只是辅助,服务端校验才是唯一防线
beego.Session 与验证码绑定时的常见坑
很多人试图把验证码答案塞进 this.StartSession(),这会导致两个严重问题:session 生命周期远长于验证码(默认 30 分钟),且 session 可能被复用或未及时销毁。
正确做法是解耦:
- 验证码答案绝不存入 session,只存在 cache 中,key 显式关联 session ID(如
captcha:sess_abc123) - 登录成功后,立刻调用
this.DestroySession(),再新建 session,防止旧 session 携带残留状态 - 如果用了 Redis 作为 session 存储,确认
session.RedisKeyPrefix和验证码 cache 的 key prefix 不冲突,否则可能误删
为什么不用 beego 验证码插件而推荐自定义实现
社区里曾有过第三方 beego-captcha 插件,但已三年无更新,不兼容 Beego v2.x 的模块化缓存接口,且默认生成的图片无噪点、无扭曲,OCR 识别率接近 100%。
实际对抗中,图形验证码的价值不在“难看”,而在“可控的不可预测性”:
- 自己用
github.com/fogleman/gg或golang/freetype绘制,随机字体大小、角度、干扰线数量,比调用黑盒函数更可控 - 避免使用固定 seed 的伪随机数(如没调
rand.Seed(time.Now().UnixNano())),否则重启后验证码序列重现 - 如果业务对安全性要求高,图形验证码应仅作辅助,主防线仍是登录频次限制 + 短信/邮箱二次确认











