基于Session实现短信登录全流程详解

发布于:2025-05-17 ⋅ 阅读:(11) ⋅ 点赞:(0)

前言

在当今的Web应用中,短信验证码登录已成为最常用的身份验证方式之一。本文将详细介绍基于Session实现短信登录的全套流程,包括技术选型、流程设计、具体实现以及安全防护措施。通过本文,您将掌握从发送验证码到完成登录的完整实现方案。

一、技术选型与架构设计

1.1 技术栈组成

  • 前端:HTML + JavaScript(Vue/React等框架均可)

  • 后端:Spring Boot 2.x

  • 短信服务:阿里云短信/腾讯云短信等第三方服务

  • Session管理:Spring Session + Redis(分布式Session方案)

1.2 架构流程图

sequenceDiagram
    participant 用户
    participant 前端
    participant 后端
    participant Redis
    participant 短信服务商
    
    用户->>前端: 输入手机号,点击获取验证码
    前端->>后端: 发送手机号(/api/sms/code)
    后端->>短信服务商: 调用短信API发送验证码
    短信服务商-->>后端: 返回发送结果
    后端->>Redis: 存储验证码(手机号:验证码,5分钟过期)
    后端-->>前端: 返回发送结果
    用户->>前端: 输入验证码,点击登录
    前端->>后端: 提交手机号和验证码(/api/login)
    后端->>Redis: 验证码比对
    alt 验证成功
        后端->>Redis: 创建用户Session
        后端-->>前端: 返回登录成功和用户信息
    else 验证失败
        后端-->>前端: 返回错误信息
    end

二、核心实现步骤

2.1 短信验证码发送

接口设计
POST /api/sms/code
请求参数:{ "phone": "13800138000" }
响应结果:{ "success": true, "message": "验证码已发送" }
服务端实现
@RestController
@RequestMapping("/api/sms")
public class SmsController {
    
    @Autowired
    private SmsService smsService;
    
    @PostMapping("/code")
    public Result sendCode(@RequestBody @Valid SmsRequest request) {
        // 1. 生成随机6位验证码
        String code = RandomStringUtils.randomNumeric(6);
        
        // 2. 发送短信(实际项目应接入短信服务商API)
        boolean sent = smsService.sendSms(request.getPhone(), 
            "您的验证码是:" + code + ",5分钟内有效");
        
        if (sent) {
            // 3. 存储验证码到Redis,5分钟过期
            redisTemplate.opsForValue().set(
                "sms_code:" + request.getPhone(), 
                code, 
                5, 
                TimeUnit.MINUTES);
            
            return Result.success("验证码已发送");
        }
        
        return Result.fail("验证码发送失败");
    }
}

// 短信服务接口
public interface SmsService {
    boolean sendSms(String phone, String content);
}

2.2 验证码校验与登录

接口设计
POST /api/login
请求参数:{ "phone": "13800138000", "code": "123456" }
响应结果:{ "success": true, "data": { "user": {...}, "token": "sessionId" } }
服务端实现
@RestController
@RequestMapping("/api")
public class LoginController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @PostMapping("/login")
    public Result login(@RequestBody @Valid LoginRequest request, 
                       HttpSession session) {
        // 1. 从Redis获取验证码
        String cacheKey = "sms_code:" + request.getPhone();
        String correctCode = redisTemplate.opsForValue().get(cacheKey);
        
        // 2. 验证码校验
        if (correctCode == null) {
            return Result.fail("验证码已过期");
        }
        if (!correctCode.equals(request.getCode())) {
            return Result.fail("验证码不正确");
        }
        
        // 3. 验证通过后清除Redis中的验证码
        redisTemplate.delete(cacheKey);
        
        // 4. 查找或创建用户
        User user = userService.findOrCreate(request.getPhone());
        
        // 5. 创建Session
        session.setAttribute("currentUser", user);
        
        // 6. 返回用户信息和SessionID
        LoginResponse response = new LoginResponse();
        response.setUser(user);
        response.setToken(session.getId());
        
        return Result.success(response);
    }
}

三、安全增强措施

3.1 防刷机制实现

// 在SmsController中添加防刷逻辑
@PostMapping("/code")
public Result sendCode(@RequestBody @Valid SmsRequest request) {
    // 防刷:1分钟内同一手机号只能发送一次
    String limitKey = "sms_limit:" + request.getPhone();
    if (redisTemplate.hasKey(limitKey)) {
        return Result.fail("操作过于频繁,请稍后再试");
    }
    
    // 发送验证码逻辑...
    
    // 设置防刷限制(1分钟)
    redisTemplate.opsForValue().set(
        limitKey, 
        "1", 
        1, 
        TimeUnit.MINUTES);
    
    return Result.success("验证码已发送");
}

3.2 验证码安全性优化

  1. 验证码复杂度:使用字母数字组合而非纯数字

  2. 请求校验:添加图形验证码二次验证(高危操作时)

  3. IP限制:对同一IP的发送频率进行限制

  4. 黑名单机制:对恶意手机号加入黑名单

四、Session管理进阶

4.1 分布式Session配置

// application.yml配置
spring:
  session:
    store-type: redis
    timeout: 1800 # 30分钟过期
  redis:
    host: localhost
    port: 6379

4.2 Session过期处理

// 登录过滤器检查Session有效性
@Component
public class LoginFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain) {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("currentUser") == null) {
            // Session过期处理
            response.sendError(401, "Session expired");
            return;
        }
        chain.doFilter(request, response);
    }
}

五、前端实现示例

5.1 获取验证码

// Vue示例
async function sendSmsCode() {
  if (!this.phone) {
    this.$message.error('请输入手机号');
    return;
  }
  
  try {
    const res = await axios.post('/api/sms/code', { phone: this.phone });
    this.$message.success('验证码已发送');
    this.startCountdown(); // 开始倒计时
  } catch (error) {
    this.$message.error(error.response.data.message);
  }
}

5.2 登录提交

async function handleLogin() {
  if (!this.phone || !this.code) {
    this.$message.error('请输入手机号和验证码');
    return;
  }
  
  try {
    const res = await axios.post('/api/login', {
      phone: this.phone,
      code: this.code
    });
    
    // 存储用户信息和Session
    localStorage.setItem('user', JSON.stringify(res.data.data.user));
    localStorage.setItem('token', res.data.data.token);
    
    this.$router.push('/home');
  } catch (error) {
    this.$message.error(error.response.data.message);
  }
}

六、常见问题解决方案

6.1 验证码错误但实际正确

  • 原因:服务器时间不同步

  • 解决:确保服务器时间准确,使用NTP同步

6.2 Session丢失问题

  • 原因:跨域或域名不一致

  • 解决

// 后端配置允许跨域携带Cookie
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://your-frontend-domain")
                .allowCredentials(true)
                .allowedMethods("*");
    }
}

6.3 短信服务商调用失败

  • 解决方案:增加重试机制和备用通道

// 带重试机制的短信发送
public boolean sendSmsWithRetry(String phone, String content, int maxRetry) {
    for (int i = 0; i < maxRetry; i++) {
        try {
            if (sendSms(phone, content)) {
                return true;
            }
        } catch (Exception e) {
            log.warn("短信发送失败,准备重试", e);
        }
        Thread.sleep(1000 * (i + 1));
    }
    return false;
}

结语

本文详细介绍了基于Session实现短信登录的全套流程,从验证码发送、校验到Session管理的各个环节。在实际项目中,还需要根据具体业务需求进行适当调整,特别是安全防护措施需要格外重视。


网站公告

今日签到

点亮在社区的每一天
去签到