为什么邮件验证比你想象的更重要
邮件验证有三个核心目的。首先,它确认用户实际控制所提供的地址——像 [email protected] 这样的拼写错误出奇地常见。其次,它减少了使用假地址或一次性地址的机器人大量自动创建账户。第三——也是最常被低估的——经过验证的邮件地址是密码重置安全的前提条件。没有验证,攻击者可以注册别人的地址并触发重置邮件。OWASP Authentication Cheat Sheet 详细涵盖了这些风险。
完整的验证流程逐步说明
邮件传输协议在 RFC 5321 中有定义——但应用层的决策完全由开发者负责。每个步骤都很重要:
- 用户提交注册表单。在服务器端验证邮件格式——不仅仅是客户端。
- 生成加密随机令牌。不是UUID,不是顺序ID,不是时间戳——真正的加密随机性,至少32字节的熵。
- 在数据库中存储令牌哈希。存储令牌的SHA-256哈希而非原始令牌——连同用户ID、创建时间戳、过期时间和"已使用"标志。
- 发送验证邮件。链接包含原始令牌作为查询参数。始终使用HTTPS。
- 用户点击链接。服务器从查询字符串接收原始令牌。
- 验证令牌。对传入令牌进行哈希,在数据库中查找匹配,检查过期和"已使用"状态。
- 成功时:将邮件地址标记为已验证,将令牌标记为已使用,让用户登录。
- 失败时:显示具体可操作的错误信息,包含请求新邮件的链接。
安全令牌生成
最常见的错误是将UUID v4用作验证令牌。UUID对数据库标识符很好,但不是为安全令牌设计的。正确方法:使用运行时的加密随机数生成器。Node.js: crypto.randomBytes(32).toString('hex')。Python: secrets.token_urlsafe(32)。.NET: RandomNumberGenerator.GetBytes(32)。OWASP Authentication Cheat Sheet 建议至少32字节(256位)的熵。
正确测试验证流程
验证流程的每次变更都应该用真实邮件发送到真实收件箱进行测试。打开一个临时邮件地址,将其复制到注册表单,创建一个测试账户,实时观察验证邮件的到达。这确定性地证明邮件实际上被投递了——而不仅仅是被提供商的API接受。
部署前最重要的测试:打开一个临时邮件地址,创建测试账户,确认验证邮件在几秒内到达,点击链接,验证账户在数据库中被标记为已确认。这个端到端测试发现投递配置问题、模板渲染问题和链接生成错误——这些在单元测试中都是不可见的。每次部署到新环境时都要运行。
邮件认证:SPF、DKIM和DMARC
没有正确的邮件认证,验证邮件会进入垃圾邮件。SPF通过DNS TXT记录授权发送服务器。DKIM添加加密签名。DMARC定义SPF或DKIM失败时的策略。使用 MXToolbox 检查三种配置。参考发送提供商关于邮件认证的文档。
常见错误
- 使用后不使令牌失效。始终设置"已使用"标志并在每次验证时检查。
- 在验证前发送欢迎邮件。只在确认验证后才激活入职序列。
- 重发端点没有速率限制。将每个地址的重发请求限制为例如每小时3次。
- 通过HTTP发送验证链接。始终使用HTTPS——无例外。
- 不记录验证事件。令牌何时创建、发送和点击——对调试至关重要。
- 对多种用途使用相同令牌。为验证、密码重置和邮件更改使用单独的令牌。