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

9.7 KiB
Raw Blame History

登录功能说明文档

功能概述

本系统实现了多种登录方式,包括:

  1. 密码登录:用户名/手机号/邮箱 + 密码
  2. 验证码登录:手机号 + 短信验证码 / 邮箱 + 邮箱验证码

架构设计

策略模式架构

登录功能采用**策略模式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. 登录会话时长

  • 勾选RememberMeRedis缓存7天前端LocalStorage长期保存
  • 未勾选RememberMeRedis缓存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绑定验证

扩展说明

如需新增登录方式(如微信登录),只需:

  1. 创建新的策略实现类
@Component
public class WechatLoginStrategy implements LoginStrategy {
    @Override
    public String getLoginType() {
        return "wechat";
    }
    // 实现其他方法...
}
  1. 前端添加对应的UI和逻辑

策略工厂会自动识别并加载新的登录策略。


文档版本v1.0
更新日期2025-11-03
作者:开发团队