Revel 框架需手动集成第三方库实现验证码,关键在于用 Redis 存储验证码(uuid 作 key、答案+过期时间作 value),禁用 session 存储,配合自定义 filter 限流、正确设置响应头与一次性校验机制。

Revel 框架本身不内置图形验证码生成器,但可以无缝集成第三方 Go 库(如 gocaptcha 或手写 image/draw + image/png 实现),配合 Revel 的 session、filter 和 controller 机制完成完整校验闭环。关键不是“Revel 能不能做”,而是“怎么避免踩坑把验证码链路做稳”。
验证码图片怎么生成并返回给前端
Revel 没有 getCaptchaImg() 这样的默认路由,你需要自己注册一个 GET 接口(比如 /captcha),在 controller 中生成 PNG 图片并直接写入 response body。
- 不要用
session.Set("captcha", code)存答案——Revel 默认 session 基于加密 cookie,大小有限(通常 ≤4KB),且无法设置 TTL;更严重的是,cookie 可被客户端篡改,校验时若只比对本地 session 值,等于形同虚设 - 必须用外部存储(推荐
Redis)存验证码值,key 用随机uuid(由后端生成并返回给前端),value 是纯文本答案 + 过期时间(如 5 分钟) - 生成图片时避免硬编码字体路径:Linux 容器里常缺
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,建议 embed 字体文件或 fallback 到font/basicfont.Face(freetype库支持) - 响应头务必加
Content-Type: image/png和Cache-Control: no-store, no-cache, must-revalidate,否则浏览器可能缓存旧验证码,用户点“换一张”却没刷新
前端传来的 uuid 和输入值怎么校验
校验动作发生在登录等表单提交的 POST 接口里(比如 LoginController.PostLogin()),不是单独开个 /check-captcha 接口——多一次请求既增加延迟,又暴露校验入口,易被扫描。
- 从 request body 或 form value 中取两个字段:
captcha_input(用户填的)和captcha_uuid(前端隐藏域或 header 传的) - 用
captcha_uuid去 Redis 查 key,查不到 = 已过期或非法请求,直接拒绝;查到则比对 value(注意忽略大小写,但不要自动转大写——有些字体里O和0渲染极相似,统一转大写反而增加误判) - 校验成功后必须立刻
DEL该 key(Redis 的GETDEL命令最安全),确保一次性使用;不删会导致重放攻击——攻击者截获一次有效 uuid+code,可反复提交 - Revel 的
Validation机制不适用于此处,因为校验依赖外部存储,不是简单的正则或长度判断
怎么防止验证码接口被刷
Revel 的 filter 是防刷第一道闸门,别等进 controller 才做限流。
- 写一个自定义 filter(比如
CaptchaRateLimitFilter),在app.Init()里注册到revel.InterceptMethod,拦截所有/captcha请求 - 用 Redis 的
INCR + EXPIRE组合实现 IP 级限流:1 分钟内最多 5 次,超限返回429 Too Many Requests并带Retry-After: 60 - 不要只靠前端 JS 防抖(
debounce)——恶意请求根本绕过 JS,服务端才是唯一可信防线 - 若项目已接入 Nginx,可在 upstream 前加
limit_req,但 Revel 层仍需兜底,避免反向代理配置遗漏导致穿透
最容易被忽略的点是:Revel 的 cookie session 默认不加密签名(app.session.secure = false 且未配 app.cookie.secret 时),如果错误地把验证码存在 session 里,攻击者可伪造任意值通过校验。哪怕你用了 Redis 存答案,也得确保 captcha_uuid 本身是密码学安全的随机串(用 crypto/rand.Read,别用 math/rand),否则 uuid 可预测,整个链路就崩了。


















