# 登录功能说明文档 ## 功能概述 本系统实现了多种登录方式,包括: 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 **作者**:开发团队