Files
schoolNews/schoolNewsServ/auth/登录功能说明.md

455 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 登录功能说明文档
## 功能概述
本系统实现了多种登录方式,包括:
1. **密码登录**:用户名/手机号/邮箱 + 密码
2. **验证码登录**:手机号 + 短信验证码 / 邮箱 + 邮箱验证码
## 架构设计
### 策略模式架构
登录功能采用**策略模式Strategy Pattern**设计,具有以下优势:
- **高扩展性**:新增登录方式只需实现策略接口
- **低耦合**:登录逻辑与业务逻辑分离
- **易维护**:每种登录方式独立实现,互不干扰
### 核心组件
#### 1. 策略接口:`LoginStrategy`
```java
public interface LoginStrategy {
String getLoginType(); // 获取登录类型
boolean validate(LoginParam loginParam); // 验证登录参数
TbSysUser findUser(LoginParam loginParam); // 查找用户
boolean verifyCredential(String input, String stored); // 验证密码
boolean verifyCaptchaWithSession(LoginParam loginParam); // 验证验证码带SessionID
}
```
#### 2. 策略实现
- **PhoneLoginStrategy**:手机号登录策略
- 支持手机号 + 密码登录
- 支持手机号 + 短信验证码登录
- **EmailLoginStrategy**:邮箱登录策略
- 支持邮箱 + 密码登录
- 支持邮箱 + 邮箱验证码登录
- **UsernameLoginStrategy**:用户名登录策略
- 支持用户名 + 密码登录
- **PasswordLoginStrategy**:通用密码登录策略
- 自动识别用户名/手机号/邮箱
#### 3. 策略工厂:`LoginStrategyFactory`
根据登录类型loginType返回对应的登录策略实例。
#### 4. 登录服务:`LoginServiceImpl`
统一的登录入口,调用策略模式处理不同的登录方式。
## 前后端交互
### 前端登录请求
#### 密码登录示例
```javascript
{
"loginType": "phone", // 登录类型username/phone/email/password
"phone": "13800138000", // 手机号phone登录时必填
"password": "123456", // 密码
"rememberMe": true, // 记住我
"agree": true // 同意协议
}
```
#### 验证码登录示例
```javascript
{
"loginType": "phone", // 登录类型
"phone": "13800138000", // 手机号
"captcha": "123456", // 验证码
"captchaId": "uuid-xxx", // 验证码会话ID
"rememberMe": true,
"agree": true
}
```
### 后端登录流程
```
1. 接收登录请求 (POST /auth/login)
2. 自动检测登录类型detectLoginType
3. 获取对应的登录策略LoginStrategyFactory
4. 验证登录参数strategy.validate
5. 查找用户strategy.findUser
6. 检查用户状态
7. 验证凭据:
- 密码登录 → strategy.verifyCredential(password, storedPassword)
- 验证码登录 → strategy.verifyCaptchaWithSession(loginParam)
8. 生成JWT令牌
9. 返回登录结果
```
## 验证码登录实现
### 验证码发送
#### 发送短信验证码
**接口**`POST /auth/send-sms-code`
**请求**
```json
{
"phone": "13800138000"
}
```
**响应**
```json
{
"code": 200,
"message": "验证码已发送",
"data": {
"sessionId": "uuid-xxx",
"message": "验证码已发送"
}
}
```
#### 发送邮箱验证码
**接口**`POST /auth/send-email-code`
**请求**
```json
{
"email": "user@example.com"
}
```
**响应**
```json
{
"code": 200,
"message": "验证码已发送到邮箱",
"data": {
"sessionId": "uuid-xxx",
"message": "验证码已发送到邮箱"
}
}
```
### 验证码验证流程(策略模式)
以手机号验证码登录为例:
```java
@Override
public boolean verifyCaptchaWithSession(LoginParam loginParam) {
// 1. 获取参数
String captchaId = loginParam.getCaptchaId(); // 会话ID
String inputCaptcha = loginParam.getCaptcha(); // 用户输入的验证码
String phone = loginParam.getPhone(); // 手机号
// 2. 参数验证
if (captchaId == null || inputCaptcha == null) {
return false;
}
// 3. 从Redis获取验证码使用SessionID
String codeKey = "sms:code:" + captchaId;
String storedValue = redisService.get(codeKey); // 格式:"手机号:验证码"
if (storedValue == null) {
return false; // 验证码已过期
}
// 4. 解析存储的值
String[] parts = storedValue.split(":");
String storedPhone = parts[0];
String storedCaptcha = parts[1];
// 5. 验证手机号和验证码是否匹配
if (!storedPhone.equals(phone) || !storedCaptcha.equals(inputCaptcha)) {
return false;
}
// 6. 验证码使用后删除
redisService.delete(codeKey);
return true;
}
```
## 安全机制
### 1. SessionID绑定
- 验证码发送时生成唯一的`sessionId`
- Redis存储格式`key="sms:code:{sessionId}", value="phone:code"`
- 验证时必须提供正确的`sessionId`才能获取验证码
- 防止验证码被其他用户使用
### 2. 双重验证
验证码登录时需要同时验证:
- 手机号/邮箱是否匹配
- 验证码是否正确
### 3. 一次性验证
验证码验证成功后立即从Redis删除防止重复使用。
### 4. 有效期限制
- 验证码有效期5分钟
- 发送频率限制60秒内只能发送一次
### 5. 登录会话时长
- **勾选RememberMe**Redis缓存7天前端LocalStorage长期保存
- **未勾选RememberMe**Redis缓存1天前端SessionStorage会话保存
### 6. 登录失败限制
- 最大尝试次数3次
- 超过限制后10分钟内无法登录
## Redis数据结构
### 验证码存储
```
# 短信验证码
key: sms:code:{sessionId}
value: {phone}:{code}
ttl: 300秒5分钟
# 邮箱验证码
key: email:code:{sessionId}
value: {email}:{code}
ttl: 300秒5分钟
```
### 发送频率限制
```
key: send:sms:{phone}
value: 1
ttl: 60秒
key: send:email:{email}
value: 1
ttl: 60秒
```
### 登录失败次数
```
key: login:attempt:{username}
value: {attemptCount}
ttl: 600秒10分钟
```
### 登录会话缓存
```
key: login:token:{userId}
value: LoginDomain对象
ttl:
- rememberMe=true: 7天604800秒
- rememberMe=false: 1天86400秒
```
## 前端实现要点
### 1. 登录模式切换
```javascript
const loginMode = ref<'password' | 'captcha'>('password'); // 密码登录/验证码登录
// 切换登录模式
const switchLoginMode = (mode) => {
loginMode.value = mode;
// 清空表单
loginForm.username = '';
loginForm.password = '';
loginForm.captcha = '';
loginForm.captchaId = '';
};
```
### 2. 验证码类型切换
```javascript
const captchaType = ref<'phone' | 'email'>('phone'); // 手机号/邮箱
// 切换验证码类型
const switchCaptchaType = (type) => {
captchaType.value = type;
// 清空相关字段
};
```
### 3. 发送验证码
```javascript
const handleSendSmsCode = async () => {
// 1. 验证手机号
if (!loginForm.phone || !phonePattern.test(loginForm.phone)) {
ElMessage.warning('请输入正确的手机号');
return;
}
// 2. 调用API发送验证码
const result = await authApi.sendSmsCode(loginForm.phone);
if (result.code === 200 && result.data) {
// 3. 保存sessionId重要
loginForm.captchaId = result.data.sessionId;
// 4. 开始倒计时
smsCountdown.value = 60;
const timer = setInterval(() => {
smsCountdown.value--;
if (smsCountdown.value <= 0) {
clearInterval(timer);
}
}, 1000);
ElMessage.success('验证码已发送');
}
};
```
### 4. 提交登录
```javascript
const handleLogin = async () => {
// 1. 表单验证
await loginFormRef.value.validate();
// 2. 调用登录API前端会自动填充loginType
const result = await store.dispatch('auth/login', loginForm);
// 3. 登录成功处理
if (result.code === 200) {
ElMessage.success('登录成功!');
router.push('/home');
}
};
```
## API端点
| 方法 | 路径 | 说明 | 是否需要认证 |
|------|------|------|-------------|
| POST | /auth/login | 用户登录 | 否 |
| POST | /auth/send-sms-code | 发送短信验证码 | 否 |
| POST | /auth/send-email-code | 发送邮箱验证码 | 否 |
| POST | /auth/logout | 用户登出 | 是 |
## 配置说明
### application.yml
```yaml
# 邮件服务配置
spring:
mail:
host: smtp.qq.com
port: 587
username: your-email@qq.com
password: your-authorization-code
properties:
mail:
smtp:
auth: true
starttls:
enable: true
# 短信服务配置
sms:
enabled: true
provider: aliyun
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
sign-name: 红色思政学习平台
template-code: SMS_xxxxx
region-id: cn-hangzhou
# 安全白名单
school-news:
auth:
white-list:
- /auth/login
- /auth/send-sms-code
- /auth/send-email-code
```
## 测试建议
### 1. 密码登录测试
- 用户名 + 密码
- 手机号 + 密码
- 邮箱 + 密码
### 2. 验证码登录测试
- 手机号 + 短信验证码
- 邮箱 + 邮箱验证码
- 验证码过期测试
- SessionID不匹配测试
- 验证码错误测试
### 3. 安全测试
- 登录失败次数限制
- 验证码发送频率限制
- 验证码一次性使用
- SessionID绑定验证
## 扩展说明
如需新增登录方式(如微信登录),只需:
1. 创建新的策略实现类
```java
@Component
public class WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "wechat";
}
// 实现其他方法...
}
```
2. 前端添加对应的UI和逻辑
策略工厂会自动识别并加载新的登录策略。
---
**文档版本**v1.0
**更新日期**2025-11-03
**作者**:开发团队