@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(loginForm.getPhone()))
return Result.fail("手机号格式错误");
// 2.校验验证码,从session中获取
//2.1 改进 从redis中获取验证码并校验
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
// 不一致,报错
return Result.fail("验证码错误");
}
//4一致,根据手机号查询用户, 并且不抛出异常 false
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getPhone, loginForm.getPhone());
User user = this.getOne(wrapper, false);
//5.判断用户是否存在,手机号注册
if (user == null) {
// 6没有查询到用户,则注册新用户
user = createUserWithPhone(loginForm.getPhone());
}
// TODO 新增 6.生成JWT
Map<String, Object> claims = new HashMap<>();
// 用户ID
claims.put(JwtClaimsConstant.USER_ID, user.getId());
// 确保密钥是 byte[] 类型
String secretKey = jwtProperties.getUserSecretKey();
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); // 推荐指定编码
// 生成 Token
String jwttoken = JWTUtil.createToken(claims, keyBytes);
// 7.2.将User对象转为HashMap存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), //beanToMap方法执行了对象到Map的转换
CopyOptions.create()
.setIgnoreNullValue(true) //BeanUtil在转换过程中忽略所有null值的属性
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); //对于每个字段值,它简单地调用toString()方法,将字段值转换为字符串。
// 7.3.存储
String tokenKey = LOGIN_USER_KEY + userDTO.getId();
// 7.4.将jwttoken存入userMap中
userMap.put("jwttoken",jwttoken);
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
// 7.4.设置token有效期
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
// 8.返回token
return Result.ok(jwttoken);
}
private User createUserWithPhone(String phone) {
// 1.创建用户对象
User user = new User();
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
user.setPhone(phone);
//2.保存用户
save(user);
return user;
}
redis实现手机号短信登录,优化成JWT 登录认证(用户登录时生成jwttoken) + Redis 自动续期,还能怎么优化呢? 进一步优化:使用双token嘛?再加上rabbitmq?
- 写回答
- 好问题 0 提建议
- 关注问题
- 邀请回答
-
2条回答 默认 最新
檀越@新空间 2025-11-27 16:45关注下午好🌅🌅🌅
本答案参考通义千问在当前的 Redis + JWT 登录认证系统中,已经实现了基本的登录、JWT 生成和 Redis 存储。为了进一步优化性能、安全性与可扩展性,可以考虑以下几种优化方案:
✅ 一、引入双 Token(Access Token + Refresh Token)
1.1 为什么需要双 Token?
- Access Token:用于每次请求的鉴权,通常有效期较短(如 5 分钟)。
- Refresh Token:用于刷新 Access Token,有效期较长(如 7 天),并存储在安全的地方(如 HttpOnly Cookie)。
1.2 优势
- 安全性更高:即使 Access Token 泄露,攻击者也无法长期访问系统。
- 用户体验更好:用户无需频繁重新登录。
- 减少 Redis 压力:通过刷新机制避免频繁更新 Token。
1.3 实现步骤
- 用户登录成功后,生成 Access Token 和 Refresh Token。
- 将 Refresh Token 存入 Redis,设置较长时间的过期时间(如 7天)。
- 在每次请求中验证 Access Token,如果失效,则使用 Refresh Token 刷新 Access Token。
- 如果 Refresh Token 也失效,要求用户重新登录。
1.4 代码示例(简化版)
// 生成双 Token public Map<String, String> generateTokens(User user) { // Access Token(短期) String accessToken = generateJwtToken(user, jwtProperties.getAccessTokenExpire(), jwtProperties.getAccessSecretKey()); // Refresh Token(长期) String refreshToken = generateJwtToken(user, jwtProperties.getRefreshTokenExpire(), jwtProperties.getRefreshSecretKey()); // 存入 Redis(可选) stringRedisTemplate.opsForValue().set("refresh_token:" + user.getId(), refreshToken, jwtProperties.getRefreshTokenExpire(), TimeUnit.MINUTES); return Map.of("access_token", accessToken, "refresh_token", refreshToken); }
✅ 二、引入 RabbitMQ 实现异步操作
2.1 为什么要用 RabbitMQ?
- 提高系统响应速度:将非关键业务逻辑(如发送短信、日志记录)异步处理。
- 解耦服务:降低模块间的依赖,提升系统稳定性。
- 消息队列保障可靠性:确保消息不会丢失。
2.2 典型应用场景
- 发送短信验证码 → 异步发送
- 记录用户行为日志 → 异步写入数据库
- 用户注册后通知其他服务 → 异步通知
2.3 实现步骤
- 在发送验证码时,将任务推送到 RabbitMQ 队列。
- 启动一个消费者监听该队列,执行实际发送逻辑。
- 确保消息消费失败时进行重试或记录错误日志。
2.4 代码示例(发送短信到 RabbitMQ)
// 生产者端:发送验证码到队列 public void sendVerificationCodeToQueue(String phone, String code) { String message = String.format("{\"phone\": \"%s\", \"code\": \"%s\"}", phone, code); rabbitTemplate.convertAndSend("sms_queue", message); } // 消费者端:监听队列并发送短信 @RabbitListener(queues = "sms_queue") public void receiveMessage(String message) { try { JSONObject json = new JSONObject(message); String phone = json.getString("phone"); String code = json.getString("code"); // 调用短信服务发送验证码 smsService.sendVerificationCode(phone, code); } catch (Exception e) { log.error("发送短信失败: {}", e.getMessage()); // 可以加入重试机制或记录失败日志 } }
✅ 三、Redis 自动续期优化
3.1 问题
目前是手动设置
expire,但若用户持续活跃,可能需要自动续期。3.2 解决方案
- 使用 Redis 的
PEXPIRE命令,定期更新 Key 的过期时间。 - 或者使用 Lua 脚本实现原子性的续期操作。
3.3 代码示例(自动续期)
public void renewTokenExpiration(Long userId) { String tokenKey = LOGIN_USER_KEY + userId; stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES); }可以在每次请求中调用此方法来延长 Token 有效期。
✅ 四、JWT 令牌签名优化
4.1 问题
当前使用的是简单的字符串密钥,不够安全。
4.2 优化建议
- 使用 HMAC-SHA256 或 RSA 等更安全的算法。
- 密钥应存储在安全配置中,而不是硬编码。
4.3 代码示例(使用 RSA 签名)
// 加载私钥 File privateKeyFile = new File("private_key.pem"); String privateKey = Files.readString(privateKeyFile.toPath()); // 生成 JWT RSAPrivateKey key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); String jwttoken = JWT.create() .withSubject(user.getId()) .withExpiresAt(new Date(System.currentTimeMillis() + 3600000)) .sign(Algorithm.RSA256(key));
✅ 五、使用 Redis 集群或哨兵模式
5.1 为什么重要?
- 高可用性:避免单点故障。
- 水平扩展:支持更大规模的并发请求。
- 数据持久化:防止数据丢失。
5.2 配置建议
- 使用 Redis Cluster 模式。
- 设置主从复制 + 哨兵监控。
✅ 六、增加 Token 黑名单机制(可选)
6.1 场景
- 用户登出后,需要立即使 Token 失效。
- 若不加黑名单,即使 Token 未过期,仍可被使用。
6.2 实现方式
- 在 Redis 中维护一个 Token 黑名单(Set 结构)。
- 每次请求时检查 Token 是否在黑名单中。
6.3 代码示例
// 登出时添加到黑名单 public void addTokenToBlacklist(String token) { stringRedisTemplate.opsForSet().add("token_blacklist", token); } // 验证 Token 是否有效 public boolean isTokenValid(String token) { return !stringRedisTemplate.opsForSet().isMember("token_blacklist", token); }
✅ 七、前端 Token 存储优化(HttpOnly Cookie)
7.1 好处
- 防止 XSS 攻击窃取 Token。
- 更安全地存储敏感信息。
7.2 实现方式
- 在响应头中设置
Set-Cookie,并标记为HttpOnly和Secure。
7.3 示例响应头
Set-Cookie: access_token=your_jwt_token; Path=/; HttpOnly; Secure
✅ 八、分布式锁解决并发问题
8.1 场景
- 多个请求同时修改同一用户数据,可能导致数据不一致。
8.2 解决方案
- 使用 Redis 的
SETNX或RedLock实现分布式锁。
8.3 代码示例(使用 Redisson)
RLock lock = redisson.getLock("user_lock:" + userId); try { lock.lock(); // 执行业务逻辑 } finally { lock.unlock(); }
✅ 总结:优化建议清单
| 优化方向 | 说明 | |---------|------| | 双 Token | 提高安全性,减少频繁登录 | | RabbitMQ | 异步处理非关键业务,提升响应速度 | | Redis 自动续期 | 避免 Token 过期中断用户会话 | | JWT 签名优化 | 使用更安全的签名算法 | | Redis 集群/哨兵 | 提升可用性和扩展性 | | Token 黑名单 | 支持用户登出后立即失效 | | HttpOnly Cookie | 更安全地存储 Token | | 分布式锁 | 防止并发操作导致的数据不一致 |
如需我为你定制具体实现方案或提供完整项目结构,请告诉我你使用的框架(如 Spring Boot、Spring Security 等),我可以进一步帮你封装代码。
解决 无用评论 打赏 举报