一、整体架构概述
这是一个基于 Spring Boot + Redis 的分布式登录系统,采用 Token 认证机制 ,实现了无状态的分布式会话管理。
核心技术栈
-**Spring Boot :Web 框架
- Redis :分布式缓存,存储验证码和用户会话
- MyBatis-Plus :数据库操作
- Hutool :工具类库
- Spring MVC 拦截器 :请求拦截和身份验证**
二、核心组件分析
1. LoginInterceptor(登录拦截器)
功能职责:
public class LoginInterceptor implements HandlerInterceptor
**- 身份验证 :验证请求是否携带有效的 token
- 用户信息恢复 :从 Redis 获取用户信息
- 会话刷新 :延长 token 有效期
- ThreadLocal 存储 :将用户信息存储到线程本地变量 工作流程**
请求到达 → 获取 token → 验证 token → 恢复用户信息 → 刷新有效期 →
放行/拦截
关键代码解析
1. Token 获取与验证
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
response.setStatus(401);
return false;
}
-
从请求头获取 authorization 字段
-
如果 token 不存在,返回 401 状态码
-
前端需要将 token 添加到请求头
2. Redis 用户信息查询
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.
opsForHash().entries(key);
if (userMap == null) {
response.setStatus(401);
return false;
}
-
根据 token 构建 Redis 键
-
使用 Hash 结构存储用户信息
-
用户不存在则拦截请求
3. 用户信息转换与存储
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new
UserDTO(), false);
UserHolder.saveUser(userDTO);
-
将 Redis 中的 Map 数据转换为 UserDTO 对象
-
存储到 ThreadLocal,方便后续业务使用
4. Token 有效期刷新
stringRedisTemplate.expire(key, RedisConstants.
LOGIN_USER_TTL, TimeUnit.MINUTES);
-
刷新 token 有效期,实现滑动过期
-
用户活跃时自动延长登录状态
2. sendCode 方法(验证码发送) 功能职责
-
手机号验证 :校验手机号格式
-
验证码生成 :生成 6 位随机验证码
-
Redis 存储 :将验证码存储到 Redis
-
模拟发送 :后端模拟短信发送 关键代码解析
1. 手机号格式验证
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式有误");
}
-
使用正则表达式验证手机号格式
-
格式错误直接返回失败信息
2. 验证码生成与存储
String code = RandomUtil.randomNumbers(6);
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY +
phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
-
生成 6 位数字验证码
-
存储到 Redis,键为 login:code:手机号
-
设置过期时间,通常 2-5 分钟
3. login 方法(用户登录) 功能职责
-
手机号验证 :校验手机号格式
-
验证码验证 :验证用户输入的验证码
-
用户查询 :根据手机号查询用户信息
-
用户创建 :新用户自动注册
-
Token 生成 :生成登录令牌并存储 关键代码解析
1. 验证码验证
String cacheCode = stringRedisTemplate.opsForValue().get
(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
return Result.fail("验证码错误");
}
-
从 Redis 获取存储的验证码
-
验证验证码是否正确或已过期
-
验证失败返回错误信息
2. 用户查询与创建
User user = query().eq("phone", phone).one();
if (user == null) {
user = createUserWithPhone(phone);
}
-
使用 MyBatis-Plus 查询用户
-
用户不存在时自动创建新用户
-
实现手机号即账号的登录方式
3. Token 生成与存储
String token = UUID.randomUUID().toString();
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.
class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,
new HashMap<>(), CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) ->
fieldValue.toString()));
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL,
TimeUnit.MINUTES);
-
生成唯一 token
-
转换用户对象为 Map 格式
-
存储到 Redis Hash 结构
-
设置 token 有效期
4. 拦截器配置
功能职责:
-
登录拦截器 :验证用户登录状态
-
Token 刷新拦截器 :自动刷新 token 有效期
-
路径排除 :配置不需要登录的路径 关键代码解析
registry.addInterceptor(new LoginInterceptor
(stringRedisTemplate))
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
registry.addInterceptor(new RefreshTokenInterceptor
(stringRedisTemplate))
.addPathPatterns("/**").order(0);
拦截器执行顺序
-
Token 刷新拦截器(order=0) :优先执行,刷新 token 有效期
-
登录拦截器(order=1) :验证用户登录状态
排除路径配置
-
公开接口:商品、优惠券、上传等
-
登录相关接口:验证码、登录接口
三、技术亮点与优势
1. 分布式支持
-
Redis 存储 :支持多服务器共享登录状态
-
无状态设计 :服务器可以水平扩展
-
负载均衡友好 :无需会话粘性
2. 安全性设计
-
Token 随机生成 :UUID 确保唯一性
-
验证码时效性 :自动过期,防止重放攻击
-
请求头认证 :避免 token 泄露到 URL
3. 用户体验优化
-
滑动过期 :活跃用户自动延长登录状态
-
自动注册 :手机号即账号,降低注册门槛
-
拦截器透明 :业务代码无需关心认证逻辑
4. 性能优化
-
Redis 缓存 :减少数据库查询
-
ThreadLocal 存储 :避免重复查询用户信息
-
Hash 结构 :高效存储和查询用户数据
四、完整登录流程
- 用户请求验证码
↓
- 后端生成验证码并存储到 Redis
↓
- 用户输入验证码并登录
↓
- 后端验证验证码
↓
- 查询/创建用户信息
↓
- 生成 Token 并存储到 Redis
↓
- 返回 Token 给前端
↓
- 前端存储 Token
↓
- 后续请求携带 Token
↓
- 拦截器验证 Token 并刷新有效期