beego验证码初始化必须在init()函数中完成,否则首次请求可能因路由未注册导致404或获取不到ID;cpt需全局声明并调用NewWithFilter注册/captcha/路径,store不可为nil,且VerifyReq校验前须确保表单已解析、缓存有效、大小写一致,并手动清理过期项。

beego/utils/captcha 初始化必须在 init() 里做
不写在 init() 函数里,第一次请求时可能拿不到验证码 ID 或图片 404。因为 cpt 是全局变量,且 NewWithFilter() 内部会注册 HTTP 路由(如 /captcha/*.png),必须在 beego 启动前完成注册。
常见错误:把初始化写在某个 Controller 的 Get() 方法里——每次请求都 new 一次,路由重复注册报 panic,且字体/缓存配置丢失。
- store 必须用
cache.NewMemoryCache()或自定义实现cache.Cache接口,不能传 nil -
FieldIDName和FieldCaptchaName默认是"captcha_id"和"captcha",若前端字段名不同,必须显式设置 - 调用
NewWithFilter("/captcha/", store)后,/captcha/xxx.png路径才生效;路径前缀不能漏掉末尾斜杠,否则匹配失败
模板里用 {{create_captcha}} 渲染,但要注意上下文
{{create_captcha}} 是 beego 内置模板函数,它会自动插入 标签和带时间戳的 onclick 刷新逻辑,但前提是:当前请求已通过 cpt 生成过至少一个验证码 ID。
如果你在 GET 页面直接渲染 {{create_captcha}} 却没提前调用 cpt.CreateCaptcha(),页面会显示空或报错(取决于 beego 版本)。
- 安全做法:在 Controller 的 GET 方法中先调用
id := cpt.CreateCaptcha(),再把id传给模板:this.Data["captcha_id"] = id - 模板中可写成:
,比依赖
{{create_captcha}}更可控 - 不要在 POST 提交后还保留原
captcha_id——校验成功后应立即失效,避免重放
VerifyReq() 校验失败的三个高频原因
cpt.VerifyReq(c.Ctx.Request) 返回 false 不一定代表用户输错,大概率是服务端状态没对上。
最常踩的坑是:没调用 req.ParseForm() 就直接读表单,或者缓存 key 已过期、被删、格式不一致。
- 确保请求是
POST且 Content-Type 为application/x-www-form-urlencoded(不是 JSON) -
VerifyReq()内部会调用req.ParseForm(),但如果之前手动调过且出错(比如 body 已被读取),会导致 form 为空 → 校验必失败 - 内存缓存(
NewMemoryCache())重启即丢,多实例部署时请求落到不同机器,必然校验失败;生产环境必须换 Redis 或其他共享存储 - 注意大小写:beego 的默认校验是严格大小写匹配,前端输入
AbC1,后端存的是abc1就会失败 —— 建议统一转小写再比对
验证码有效期和清理必须手动干预
beego 的 cpt.Expiration 默认是 600 秒(10 分钟),但这个值只影响新生成的验证码,不会自动清理过期项。内存缓存不主动驱逐,Redis 也得靠 SET EX 才能真正过期。
更危险的是:verify 成功后,VerifyReq() 并不会自动删除该验证码(旧版 beego 甚至完全不删),导致同一验证码可重复使用。
- 务必在校验逻辑后加手动清理:
cpt.store.Delete(cpt.key(id)),key 构造规则是cpt.CachePrefix + ":" + id - 如果用了自定义 store(如 Redis),需确保
Delete()方法真正执行了 DEL 命令,而不是空实现 - 别依赖 “用户关闭页面就自动失效” —— 缓存不清理,攻击者拿着旧 ID + 正确值仍能通过校验
真正难的不是生成那张图,而是让每个验证码 ID 在整个生命周期里:可生成、可传输、可绑定上下文、可限时存在、可精准销毁。少一步,安全水位就掉一截。











