captcha.NewLen生成的ID配合VerifyString使用后立即失效,防止重放攻击;验证失败须重新生成ID并更新前端,不可复用旧ID,生产环境必须用Redis替代内存store以支持多实例共享。

captcha.NewLen 生成的 ID 必须配合 VerifyString 使用,且验证后失效
调用 captcha.VerifyString 后,该 ID 对应的验证码答案会被立即清除(dchest/captcha 默认行为),后续再用同一 ID 校验会始终返回 false。这不是 bug,是设计使然 —— 防止重放攻击。
所以你不能在登录成功后还留着这个 ID 做二次校验,也不能在前端“点一次刷新多次”却只生成一次 ID。常见错误是:用户输错验证码,后端没重新生成新 ID 就直接返回错误,导致前端继续拿旧 ID 提交,永远过不了。
- 每次验证失败,必须调用
captcha.NewLen生成新 ID,并更新前端的src地址和隐藏域值 - 不要把 ID 存 Session 或 Cookie 里跨请求复用;它本就是一次性、带 TTL 的凭证
- 如果需要支持“验证失败但保留当前图片”,得自己缓存答案(比如 Redis + 自定义 key),绕过
captcha内置清理逻辑
/captcha/{id} 接口必须支持 reload 参数触发刷新
前端点击换图时,浏览器可能因缓存直接复用旧响应,导致图片不变。Iris 控制器里靠 c.Ctx.URLParam("t") != "" 判断是否需调用 captcha.Reload(id),本质是强制让服务端丢弃旧答案、生成新图形,同时保持 URL 路径不变(便于前端只改 src 后缀)。
这个 reload 机制依赖 captcha.Reload,它不会新建 ID,而是复用原 ID 生成新答案并覆盖存储 —— 所以前端无需更换 ID,只要加个时间戳参数就能刷新图片和答案。
- 前端换图代码要带随机 query,例如:
this.src = "/captcha/" + id + "?t=" + Date.now() - 后端响应头必须禁用缓存:
Cache-Control: no-cache, no-store, must-revalidate,否则 reload 无效 -
captcha.Reload不会返回新 ID,所以前端不能指望它给你发新 ID 字符串;ID 始终由首次生成时确定
VerifyCaptcha 工具函数不能直接传表单值,得先 Trim 和 ToUpper
dchest/captcha 默认生成的验证码是纯数字(如 "4829"),但用户输入可能带空格、换行或大小写(比如前端 input 没设 type="text" 或用了自动填充)。直接比对会失败。
工具包里的 VerifyCaptcha 函数如果没做清洗,就会在校验环节掉坑。尤其当验证码含字母时(比如启用了 captcha.RandomDigits = false),大小写不一致会导致 VerifyString 返回 false。
- 务必在调用
captcha.VerifyString前对用户输入做strings.TrimSpace和strings.ToUpper - 生成时若用
captcha.NewLen(4),默认只生成数字;如需字母,得提前设置captcha.SetCustomStore或改源码,不推荐 - 避免前端用
input[type="number"],它会过滤掉字母,导致字母型验证码无法输入
验证码路径 /pub/captcha/ 必须映射为静态文件路由
Iris 默认不自动服务 /pub/captcha/xxx 这类路径,哪怕你生成了 PNG 文件写到磁盘。因为 dchest/captcha 是内存生成、流式响应,不是真写文件 —— 所以你看到的 Path 字段只是逻辑路径,不是物理路径。
真正起作用的是控制器里 responseCaptchaImage 方法,它用 http.ServeContent 直接把 bytes 写进 ResponseWriter。这意味着:你不需要配置静态文件服务,但必须确保该路由被 MVC 控制器正确注册,且没被其他中间件拦截(比如 JWT 鉴权中间件不该拦 /captcha/*)。
- 检查
BeforeActivation是否注册了GET /captcha/{captcha_id} - 确认没有全局中间件对
/captcha/路径做重定向、鉴权或 body 解析(它不走 form 解析) - 如果用
app.HandleDir映射了/pub,反而可能冲突 —— 因为/pub/captcha/xxx会被当成静态文件去找,而实际并不存在该文件
最易忽略的一点:验证码 ID 的生命周期完全由 dchest/captcha 内部的内存 map + 定时清理控制,默认 TTL 是 10 分钟。如果你的应用开了多个 Iris 实例(比如负载均衡),这个 map 不共享,会导致用户在 A 实例生成的 ID,在 B 实例上永远验证失败。生产环境必须用 Redis 替换默认 store,否则横向扩展就废了。











