安全生成6位验证码须用crypto/rand替代math/rand,避免可预测性;字符集推荐"23456789abcdefghjklmnpqrstuvwxyz",长度4–6位,生成后存redis并设5分钟过期。

验证码生成函数怎么写才安全又可用
直接用 rand 生成纯数字字符串是常见错误——它不加密安全,且默认种子固定导致可预测。Gin 本身不提供验证码逻辑,得自己封装,关键在于:用 crypto/rand 替代 math/rand,字符集排除易混淆字符(如 0/O/l/1),长度建议 4–6 位。
- 别用
rand.Intn(),改用crypto/rand.Read()获取真随机字节 - 字符集推荐
"23456789ABCDEFGHJKLMNPQRSTUVWXYZ"(去掉 0/O/I/1) - 生成后立即存入 Redis,设置 5 分钟过期,key 建议带前缀如
"captcha:" + uuid - 避免在 handler 里直接拼接 HTML 或 base64 图片——返回纯文本验证码+ID,前端自行渲染
Gin 路由怎么返回验证码 ID 和图片流
不能把图片 base64 塞进 JSON 返回体里,既增大体积又难缓存;也不该用重定向跳转图片地址(跨域、Referer 问题多)。正确做法是:一个接口返回 { "captcha_id": "xxx", "expires_in": 300 },另一个接口用该 ID 流式输出 PNG。
- 第一个接口(如
GET /api/captcha)生成验证码,存 Redis,返回 JSON - 第二个接口(如
GET /api/captcha/image/:id)查 Redis,命中则调用绘图库(如github.com/fogleman/gg)绘制,w.Header().Set("Content-Type", "image/png")后直接img.Encode(w, png) - 务必校验
:id是否为空、是否匹配 Redis key 格式,防止路径遍历或空指针 panic - 不要在图片生成时再查一次 session —— Gin 没内置 session,全靠你传的 captcha_id 查 Redis
前端怎么配合做刷新和校验
用户点“换一张”时,不是只换图,必须同步更新本地记录的 captcha_id,否则提交时拿旧 ID 去校验必然失败。后端校验逻辑也容易漏掉时间判断。
- 每次调用
/api/captcha成功后,把返回的captcha_id存到组件 state 或 hidden input 中 - 表单提交时,把
captcha_id和用户输入的captcha_code一起发过去(别只传 code) - 后端校验必须三步:查 Redis → 比对值 → 删掉该 key(防重放)
- Redis 查不到或值不匹配,统一返回
400 Bad Request+{"error": "captcha_invalid"},别暴露“已过期”或“不存在”细节
为什么验证码总被识别或频繁失效
不是算法不够复杂,而是部署和配置出了问题:Redis 过期策略没生效、图片字体太规则、或者测试时反复用同一 IP 触发限流。
- 检查 Redis 的
maxmemory-policy是否为allkeys-lru,避免 key 没过期就被踢出 - 绘图时加轻微噪声(
gg.SetRGBA(0.1, 0.1, 0.1, 0.1)画几条干扰线)、字符旋转 ±15°,但别过度——OCR 工具能绕过强干扰,反而影响真实用户 - 本地开发用
localhost测试时,浏览器可能复用连接导致验证码不刷新,加时间戳参数强制更新:/api/captcha/image/abc123?t=1717023456 - 上线后如果大量失败,先看 Redis 监控:key 数量突增说明没删干净,TTL 突降说明过期没生效
真正麻烦的从来不是画图或生成,是 Redis key 生命周期管理、前后端 ID 同步时机、以及过期与删除的竞态——这些地方一错,验证码就形同虚设。











