前言
在当今的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 验证码安全性优化
验证码复杂度:使用字母数字组合而非纯数字
请求校验:添加图形验证码二次验证(高危操作时)
IP限制:对同一IP的发送频率进行限制
黑名单机制:对恶意手机号加入黑名单
四、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管理的各个环节。在实际项目中,还需要根据具体业务需求进行适当调整,特别是安全防护措施需要格外重视。