Go语言需用crypto/rand生成安全验证码,避免rand.Intn();字符集应剔除易混淆字符,配合显式随机读取实现4–6位字母数字组合。

Go 语言本身不内置图形验证码(CAPTCHA)生成能力,但用标准库就能实现轻量、无依赖的字符型验证码——关键是控制好随机性、存储和时效性。
用 rand 生成安全的 4–6 位字母数字组合
别用 rand.Intn() 直接拼字符串,它默认基于非加密种子,容易被预测。必须显式调用 rand.Read() 配合 crypto/rand:
import (
"crypto/rand"
"math/big"
)
func generateCode(length int) string {
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // 去掉易混淆字符
var code []byte
for i := 0; i < length; i++ {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
code = append(code, chars[n.Int64()])
}
return string(code)
}
-
crypto/rand.Reader是操作系统提供的真随机源,比math/rand更适合安全场景 - 显式剔除
I、O、0、1等易读错字符,减少用户输入投诉 - 长度建议固定为 4 或 6 位:太短易暴力,太长影响体验且不提升实质安全性
用 sync.Map 或 Redis 存储验证码并设过期时间
内存存储选 sync.Map 仅适用于单机调试;生产环境必须用 Redis,否则多实例下验证必然失败:
// 示例:Redis 存储(用 github.com/go-redis/redis/v9) client.Set(ctx, "captcha:"+sessionID, code, 5*time.Minute)
- 键名加前缀如
captcha:,避免和其他业务 key 冲突 - 过期时间设为 5 分钟足够——再长增加暴力重试窗口,再短引发用户反复刷新
- 务必在验证成功后立即
Del对应 key,防止重复使用(replay attack) - 如果坚持用内存,
sync.Map无法自动过期,得自己起 goroutine 定时清理,不推荐
验证时严格区分大小写并校验时效性
前端传来的验证码常带空格或换行,后端不做清洗就直接比对会失败;同时 Redis 的 GET 返回空说明已过期或不存在:
立即学习“go语言免费学习笔记(深入)”;
func verifyCode(ctx context.Context, sessionID, input string) bool {
val, err := client.Get(ctx, "captcha:"+sessionID).Result()
if err == redis.Nil {
return false // key 不存在(已过期或从未生成)
}
if err != nil {
log.Printf("redis get error: %v", err)
return false
}
return strings.TrimSpace(input) == val // 忽略首尾空白
}
-
strings.TrimSpace()必须加,移动端软键盘常误触空格 - 不要用
strings.EqualFold()——大小写不敏感会显著降低熵值,等同于少用一半字符集 - Redis 返回
redis.Nil表示 key 不存在,这是判断“过期”的唯一可靠方式,别查 TTL
真正难的不是生成那几行代码,而是存储生命周期管理:key 命名是否冲突、过期是否准时、验证后是否及时销毁、并发请求下是否出现竞态——这些细节没对齐,验证码就只是个摆设。










