9.7 KiB
9.7 KiB
登录功能说明文档
功能概述
本系统实现了多种登录方式,包括:
- 密码登录:用户名/手机号/邮箱 + 密码
- 验证码登录:手机号 + 短信验证码 / 邮箱 + 邮箱验证码
架构设计
策略模式架构
登录功能采用**策略模式(Strategy Pattern)**设计,具有以下优势:
- 高扩展性:新增登录方式只需实现策略接口
- 低耦合:登录逻辑与业务逻辑分离
- 易维护:每种登录方式独立实现,互不干扰
核心组件
1. 策略接口:LoginStrategy
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
统一的登录入口,调用策略模式处理不同的登录方式。
前后端交互
前端登录请求
密码登录示例
{
"loginType": "phone", // 登录类型:username/phone/email/password
"phone": "13800138000", // 手机号(phone登录时必填)
"password": "123456", // 密码
"rememberMe": true, // 记住我
"agree": true // 同意协议
}
验证码登录示例
{
"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
请求:
{
"phone": "13800138000"
}
响应:
{
"code": 200,
"message": "验证码已发送",
"data": {
"sessionId": "uuid-xxx",
"message": "验证码已发送"
}
}
发送邮箱验证码
接口:POST /auth/send-email-code
请求:
{
"email": "user@example.com"
}
响应:
{
"code": 200,
"message": "验证码已发送到邮箱",
"data": {
"sessionId": "uuid-xxx",
"message": "验证码已发送到邮箱"
}
}
验证码验证流程(策略模式)
以手机号验证码登录为例:
@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. 登录模式切换
const loginMode = ref<'password' | 'captcha'>('password'); // 密码登录/验证码登录
// 切换登录模式
const switchLoginMode = (mode) => {
loginMode.value = mode;
// 清空表单
loginForm.username = '';
loginForm.password = '';
loginForm.captcha = '';
loginForm.captchaId = '';
};
2. 验证码类型切换
const captchaType = ref<'phone' | 'email'>('phone'); // 手机号/邮箱
// 切换验证码类型
const switchCaptchaType = (type) => {
captchaType.value = type;
// 清空相关字段
};
3. 发送验证码
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. 提交登录
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
# 邮件服务配置
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绑定验证
扩展说明
如需新增登录方式(如微信登录),只需:
- 创建新的策略实现类
@Component
public class WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "wechat";
}
// 实现其他方法...
}
- 前端添加对应的UI和逻辑
策略工厂会自动识别并加载新的登录策略。
文档版本:v1.0
更新日期:2025-11-03
作者:开发团队