优先选用 dchest/captcha 库,它默认启用真随机数、动态干扰线和多字体混合,比手写更安全可靠;需动态生成 captcha id(如 captcha.newlen(4)),验证后自动失效,须配合 session/redis 存储并禁用缓存。

用 dchest/captcha 生成图片验证码,别自己画
直接手写图形验证码容易出安全漏洞(比如字体可预测、噪点强度固定),dchest/captcha 是 Go 社区最成熟的方案,它默认用真随机数 + 动态干扰线 + 多字体混合,比 DIY 更可靠。Iris 没内置验证码支持,所以得靠它搭桥。
关键点:生成时不要传固定 captcha_id,要用 captcha.New() 或 captcha.NewLen(n) 动态创建,否则多个用户会共享同一张图,验证时互相干扰。
-
captcha.New()生成 6 位随机字符串 ID,对应默认 6 位数字+字母组合的验证码文本 -
captcha.NewLen(4)可指定长度,适合移动端小屏输入场景 - ID 生成后必须立刻存到 session 或 Redis 中(Iris 推荐用
ctx.Session().Set()),前端提交时带着这个 ID 和用户输入的文本一起过来
captcha.VerifyString() 验证后必须立即失效
这是最容易翻车的地方:captcha.VerifyString(id, user_input) 成功返回 true 后,该 id 对应的验证码就自动过期了——下一次再调用 VerifyString 必然失败。你不能指望它“验证一次还能再验一次”。
如果登录失败还想让用户重试,必须在验证失败后主动调用 captcha.Reload(id) 生成新图,否则前端刷新图片也没用(因为旧 ID 已被清空)。
- 验证前先检查
captcha.IdExists(id),避免空 ID 导致 panic - 验证失败时,不要只返回错误,要同步调用
captcha.Reload(id)并把新 ID 返回给前端 - 别在验证逻辑里做耗时操作(比如查 DB),否则用户卡在“等验证”时,验证码可能已超时(默认 10 分钟)
Iris 路由暴露 /captcha/{id} 时注意缓存和响应头
浏览器或 CDN 缓存图片会导致用户看到旧验证码,尤其在点击“换一张”时毫无反应。必须显式禁用缓存:
- 设置
Cache-Control: no-cache, no-store, must-revalidate - 加
Pragma: no-cache和Expires: 0 - 响应体用
bytes.Buffer构造,别直接Write到ResponseWriter,否则无法控制 Content-Type - 扩展名不是必须的,但建议统一用
.png,避免某些安卓 WebView 对无扩展名图片解析失败
示例关键头设置:
W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")<br>W.Header().Set("Content-Type", "image/png")
前端换图逻辑要带时间戳,且绑定新 ID
前端常见错误是只改 src="/captcha/abc123",却不更新 URL 参数。一旦用户多次点击“换一张”,所有请求都打到同一个 id 上,后端 Reload 失败,前端一直显示裂图。
正确做法是每次点击都生成新 id,并拼进 URL:
- 首次加载:调用
/api/captcha/new获取一个新id,存入 JS 变量 - 换图点击事件:
img.src = '/captcha/' + newId + '?t=' + Date.now() - 登录提交时,把当前
id和用户输入的验证码文本一起 POST,服务端用这个id去验证
漏掉 ?t=... 在部分 iOS Safari 下会强制走缓存,哪怕你后端头设全了也白搭。
真正麻烦的不是生成或验证本身,而是 ID 的生命周期管理——它横跨前端内存、HTTP 请求、后端 session、captcha 内部状态四层。任一层没对齐,就会出现“明明输对了却说错”或者“点十次还是同一张图”的问题。











