halo/docs/email-verification/README.md

130 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 背景
在 Halo 中,邮箱作为用户主要的身份识别和通信方式,不仅有助于确保用户提供的邮箱地址的有效性和所有权,还对于减少滥用行为、提高账户安全性以及确保用户可以接收重要通知(如密码重置、注册新账户、确认重要操作等)至关重要。
邮箱验证是用户管理过程中的一个关键组成部分,可以帮助维护了一个健康、可靠的用户基础,并且为系统管理员提供了一个额外的安全和管理手段,因此实现一个高效、安全且用户友好的邮箱验证功能至关重要。
## 需求
1. **用户注册验证**:确保新用户在注册过程中提供有效的邮箱地址。邮箱验证作为新用户激活其账户的必要步骤,有助于减少虚假账户和提升用户的整体质量。
2. **密码重置和安全操作**:在用户忘记密码或需要重置密码时,向已验证的邮箱地址发送密码重置链接来确保安全性。
3. **用户通知**:验证邮箱地址有助于确保用户可以接收到重要通知,如文章被评论、有新回复等。
## 目标
- 支持用户在修改邮箱后支持重新进行邮箱验证。
- 允许用户在未收到邮件或邮件过期时重新请求发送验证邮件。
- 避免邮件通知被滥用,如频繁发送验证邮件,需要添加限制。
- 验证码过期机制,以确保验证邮件的有效性和安全性。
## 非目标
- 不考虑用户多邮箱地址的验证。
## 方案
### EmailVerificationManager
通过使用 guava 提供的 Cache 来实现一个 EmailVerificationManager 来管理邮箱验证的缓存。
```java
class EmailVerificationManager {
private final Cache<UsernameEmail, Verification> emailVerificationCodeCache =
CacheBuilder.newBuilder()
.expireAfterWrite(CODE_EXPIRATION_MINUTES, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
private final Cache<UsernameEmail, Boolean> blackListCache = CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofHours(1))
.maximumSize(1000)
.build();
record UsernameEmail(String username, String email) {
}
@Data
@Accessors(chain = true)
static class Verification {
private String code;
private AtomicInteger attempts;
}
}
```
当用户请求发送验证邮件时,会生成一个随机的验证码,并将其存储在缓存中,默认有效期为 10 分钟,当十分钟内用户未验证成功,验证码会自动过期被缓存清除。
用户可以在十分钟内重新请求发送验证邮件,此时会生成一个新的验证码有效期依然为 10 分钟。但会限制用户发送频率,同一个用户的邮箱发送验证邮件的时间间隔不得小于
1 分钟,以防止滥用。
当用户请求验证邮箱时,会从缓存中获取验证码,如果验证码不存在或已过期,会提示验证码无效或已过期,如果验证码存在且未过期,会进行验证码的比对,如果验证码不正确,会提示验证码无效,如果验证码正确,会将用户邮箱地址标记为已验证,并从缓存中清除验证码。
如果用户反复使用 code 验证邮箱,会记录失败次数,如果达到了默认的最大尝试次数(默认为 5 次),将被加入黑名单,需要 1
小时后才能重新验证邮件。
根据上述规则:
- 每个验证码有10分钟的有效期。
- 在这10分钟内如果失败次数超过5次用户会被加入黑名单禁止验证1小时。
- 如果在10分钟内尝试了5次且失败然后请求重新发送验证码可以再次尝试5次。
那么:
- 在不触发黑名单的情况下每10分钟可以尝试5次。
- 一小时内,可以尝试 (60/10) * 5 = 30 次前提是每10分钟都请求一次新的验证码。
- 但是如果在任何10分钟内尝试超过5次则会被禁止1小时。
因此,为了最大化尝试次数而不触发黑名单,每小时可以尝试 30 次预计一天内24h最多可以尝试 720 次验证码。
验证码的组成为随机的 6 为数字,可能组合总数:一个 6 位数字的验证码可以从 000000 到 999999总共有 10 <sup>6</sup> 种可能的组合。
10 <sup>6</sup> / 720 = 1388因此预计最坏情况下需要 1388 天可以破解验证码。这个时间足够长,可以认为非常安全的。
### 提供 APIs 用于处理验证请求
- `POST /apis/v1alpha1/users/-/send-verification-email`:用于请求发送验证邮件来验证邮箱地址。
- `POST /apis/v1alpha1/users/-/verify-email`:用于根据邮箱验证码来验证邮箱地址。
以上两个 APIs 认证用户都可以访问,但会对请求进行限制,请求间隔不得小于 1 分钟,以防止滥用。
并且会在用户个人资料 API 中添加 emailVerified 字段,用于标识用户邮箱是否已验证。
### 验证码邮件通知
只会通过用户请求验证的邮箱地址发送验证邮件,并且提供了以下变量用户自定义通知模板:
- **username**: 请求验证邮件地址的用户名。
- **code**: 验证码。
- **expirationAtMinutes**: 验证码过期时间(分钟)。
验证邮件默认模板示例内容如下:
```markdown
guqing 你好:
使用下面的动态验证码OTP验证您的电子邮件地址。
277436
动态验证码的有效期为 10 分钟。如果您没有尝试验证您的电子邮件地址,请忽略此电子邮件。
guqing's blog
```
### 安全和异常处理
- 确保所有敏感数据安全传输,当验证码不正确或过期时,只应该提示一个通用的错误信息防止用户猜测或爆破验证码。
- 异常提示多语言支持。
## 结论
通过实施上述方案,考虑到了以下情况:
1. 新邮箱验证请求
2. 用户邮箱地址更新
3. 用户请求重新发送验证邮件
4. 邮件发送失败
5. 验证码有效期
6. 发送频率限制
7. 验证状态的指示和反馈
我们将能够提供一个安全、可靠且用户友好的邮箱验证功能。