1. 短信登录功能的技术实现背景
在移动互联网应用中,短信验证码登录已经成为用户身份验证的主流方式之一。相比传统账号密码登录,短信登录具有以下优势:
- 无需记忆复杂密码
- 验证过程快速便捷
- 安全性更高(动态验证码)
- 可直接获取用户手机号信息
基于Redis实现短信登录功能,主要解决以下核心问题:
- 验证码的生成与存储
- 验证码的有效期管理
- 登录状态的维持
- 防止验证码被暴力破解
2. Redis在短信登录中的核心作用
2.1 验证码存储设计
采用Redis的String类型存储验证码是最常见的方案:
bash复制SET sms:login:13800138000 123456 EX 300
这条命令实现了:
- Key设计:
sms:login:{手机号}的格式 - Value:6位随机数字验证码
- 过期时间:300秒(5分钟)
关键设计要点:
- Key前缀
sms:login:实现业务隔离- 手机号作为Key后缀确保唯一性
- EX参数自动处理过期逻辑
2.2 并发请求处理
为防止同一手机号频繁获取验证码,需要添加限制逻辑:
bash复制# 使用INCR实现计数器
INCR sms:limit:13800138000
EXPIRE sms:limit:13800138000 60
# 获取当前计数
GET sms:limit:13800138000
典型限制策略:
- 60秒内最多发送3次
- 24小时内最多发送10次
2.3 登录状态保持
成功登录后,使用Redis存储会话信息:
bash复制# 生成随机token作为登录凭证
SET login:token:abcd1234 '{"userId":1001,"phone":"13800138000"}' EX 86400
这种设计实现了:
- 无状态会话管理
- 灵活的过期时间控制
- 支持分布式部署
3. 完整实现流程
3.1 发送验证码接口实现
Java示例代码:
java复制public Result sendCode(String phone) {
// 1. 校验手机号格式
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误");
}
// 2. 检查发送频率
String limitKey = "sms:limit:" + phone;
Long count = redisTemplate.opsForValue().increment(limitKey);
if (count != null && count == 1) {
redisTemplate.expire(limitKey, 1, TimeUnit.MINUTES);
}
if (count > 3) {
return Result.fail("发送过于频繁");
}
// 3. 生成并存储验证码
String code = RandomUtil.randomNumbers(6);
String codeKey = "sms:login:" + phone;
redisTemplate.opsForValue().set(codeKey, code, 5, TimeUnit.MINUTES);
// 4. 模拟发送短信(实际项目调用短信服务商API)
log.debug("发送短信验证码:{} -> {}", phone, code);
return Result.ok();
}
3.2 登录验证接口实现
java复制public Result login(LoginFormDTO loginForm) {
// 1. 校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误");
}
// 2. 校验验证码
String cacheCode = redisTemplate.opsForValue().get("sms:login:" + phone);
String inputCode = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(inputCode)) {
return Result.fail("验证码错误");
}
// 3. 查询用户信息(不存在则自动注册)
User user = query().eq("phone", phone).one();
if (user == null) {
user = createUserWithPhone(phone);
}
// 4. 生成token作为登录凭证
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 5. 存储用户信息到Redis
String tokenKey = "login:token:" + token;
redisTemplate.opsForValue().set(tokenKey,
JSONUtil.toJsonStr(userDTO),
30, TimeUnit.MINUTES);
// 6. 删除已使用的验证码
redisTemplate.delete("sms:login:" + phone);
return Result.ok(token);
}
4. 安全增强方案
4.1 验证码安全防护
| 风险类型 | 防护措施 | 实现方式 |
|---|---|---|
| 暴力破解 | 验证码复杂度 | 使用6位数字+字母组合 |
| 重放攻击 | 单次有效性 | 验证后立即删除Redis中的验证码 |
| 接口滥用 | 频率限制 | Redis计数器+IP限制 |
4.2 会话安全设计
-
Token生成策略:
- 使用UUID+时间戳+随机数组合
- 建议格式:
设备类型_用户ID_随机串
-
会话续期机制:
java复制// 每次请求后刷新token有效期
Boolean success = redisTemplate.expire(
tokenKey,
30,
TimeUnit.MINUTES
);
- 多设备登录管理:
bash复制# 使用Hash存储多设备token
HSET login:user:1001 mobile abc123
HSET login:user:1001 web xyz456
5. 性能优化实践
5.1 Redis管道化操作
批量执行Redis命令提升性能:
java复制redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
connection.set(
("sms:login:" + phone).getBytes(),
code.getBytes(),
Expiration.seconds(300),
RedisStringCommands.SetOption.UPSERT
);
connection.incr(("sms:limit:" + phone).getBytes());
connection.expire(("sms:limit:" + phone).getBytes(), 60);
return null;
});
5.2 缓存预热策略
对于高频访问用户,提前加载会话信息:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
public void preheatActiveUsers() {
// 查询最近7天活跃用户
List<User> activeUsers = userMapper.selectActiveUsers(7);
activeUsers.forEach(user -> {
String token = generateToken(user);
String tokenKey = "login:preheat:" + token;
redisTemplate.opsForValue().set(
tokenKey,
JSONUtil.toJsonStr(user),
2, TimeUnit.HOURS
);
});
}
6. 异常处理与监控
6.1 短信服务降级方案
当短信服务不可用时,启用备用方案:
java复制// 配置降级开关
@Value("${sms.downgrade}")
private Boolean smsDowngrade;
public Result sendCode(String phone) {
// ...省略其他逻辑
if (smsDowngrade) {
// 将验证码写入本地缓存作为降级方案
localCache.put("sms:login:" + phone, code);
return Result.ok("短信服务维护中,请记录验证码:" + code);
}
// 正常发送短信
smsService.send(phone, code);
return Result.ok();
}
6.2 Redis监控指标
关键监控项配置示例:
bash复制# Redis慢查询监控
slowlog-log-slower-than 10000
slowlog-max-len 128
# 内存使用告警
maxmemory 2gb
maxmemory-policy allkeys-lru
建议监控的指标包括:
- 验证码相关命令的QPS
- 登录token存储的内存占用
- 验证码验证的响应时间P99
7. 实际部署建议
7.1 Redis部署架构
根据业务规模选择合适架构:
| 规模 | 架构方案 | 特点 |
|---|---|---|
| 小型 | 单节点 | 简单易维护 |
| 中型 | 主从复制 | 读写分离 |
| 大型 | Redis Cluster | 自动分片 |
7.2 持久化配置
建议配置:
bash复制# RDB持久化
save 900 1
save 300 10
save 60 10000
# AOF持久化
appendonly yes
appendfsync everysec
对于登录会话数据:
- 开启RDB快照防止全量丢失
- 结合AOF确保操作可重放
- 会话数据可容忍少量丢失
8. 扩展功能实现
8.1 多因素认证增强
结合短信验证码+密码的双因素认证:
java复制public Result loginWith2FA(LoginFormDTO form) {
// 1. 先验证短信验证码
Result codeResult = verifyCode(form.getPhone(), form.getCode());
if (!codeResult.isSuccess()) {
return codeResult;
}
// 2. 再验证密码
User user = getUserByPhone(form.getPhone());
if (!passwordEncoder.matches(form.getPassword(), user.getPassword())) {
return Result.fail("密码错误");
}
// ...后续登录逻辑
}
8.2 行为验证集成
防止机器人攻击,集成行为验证:
java复制public Result sendCode(String phone, String captchaVerification) {
// 先验证行为验证码
boolean valid = captchaService.verify(captchaVerification);
if (!valid) {
return Result.fail("行为验证失败");
}
// ...后续发送验证码逻辑
}