注册后立即生成激活令牌并存入Redis,使用activation:{user_id}键、UUID随机值、30分钟TTL;激活链接需HMAC签名防篡改,校验须严格检查时效、签名与Redis存在性,失败统一跳转/activate/fail,成功则删key并更新用户激活状态。

注册后立即生成激活令牌并存入Redis
用户提交注册表单后,不能直接写死过期时间或用本地内存存token——Fiber本身无内置会话存储,必须外接持久化方案。推荐用Redis存activation:{user_id},值为随机字符串,TTL设为30分钟:
-
token := uuid.NewString()(用github.com/google/uuid) - Redis写入:
redisClient.Set(ctx, "activation:"+userID, token, 30*time.Minute) - 务必检查
redisClient.Set返回错误,网络抖动时写入失败需中止流程并返回500 - 别把token塞进数据库users表——激活是临时状态,混入主表会污染schema且增加查询负担
发送邮件时拼接带签名的激活链接
激活链接不是裸露的/activate?token=xxx,必须防篡改。用Fiber自带的app.Config().Secret做HMAC签名:
- 构造原始数据:
userID + "|" + token + "|" + timestamp(timestamp用秒级Unix时间) - 签名:
hmac.New(sha256.New, []byte(app.Config().Secret)),再Sum(nil)转hex - 最终URL:
https://yoursite.com/activate?u=<code>userID&t=token&s=signature&e=timestamp - 邮件模板里禁用HTML脚本,纯文本+链接;Gmail等客户端可能屏蔽未验证域名的邮件,上线前务必配置SPF/DKIM
激活路由校验签名、时效与Redis存在性
GET /activate处理逻辑有三层校验,缺一不可:
- 解析
u、t、s、e参数,任一缺失返回400 - 检查
e是否超时(abs(time.Now().Unix()-e) > 1800),防重放 - 用
userID和token查Redis:redisClient.Get(ctx, "activation:"+userID),值必须完全匹配token;查不到或不匹配即404 - 校验通过后立刻
Del该key(防止重复激活),再更新users表is_active字段为true - 别在DB事务里做Redis操作——Redis不是ACID存储,先删Redis再更DB,失败则人工对账
前端跳转与错误提示要区分真实原因
用户点击邮件链接后,页面不该暴露“token不存在”或“已过期”这种细节,避免被枚举攻击:
立即学习“go语言免费学习笔记(深入)”;
- 所有校验失败统一跳转到
/activate/fail,返回静态页提示“链接无效或已过期,请重新注册” - 成功激活跳转
/activate/success,显示“邮箱已验证,现在可以登录” - 后端记录完整日志:
log.Info("activation_attempt", "user_id", userID, "reason", "expired"),但不在HTTP响应体泄露 - 浏览器地址栏保留原始query参数,但Fiber中间件应确保
/activate路径不被搜索引擎抓取(加X-Robots-Tag: noindex)
systemd-timesyncd定期校准;Redis挂了会导致所有新注册用户无法激活,得配好哨兵或Cluster,并在注册接口加降级逻辑——比如降级为发短信验证码(如果已有短信通道)。


















