个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客
存在session共享问题的 HttpSession实现方式 在上一篇文章: ①实现基于session的登录流程:发送验证码、登录注册、校验登陆状态-CSDN博客
/**
* Redis实现共享Session登录
* @param loginForm
* @param session
* @return
*/
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1. 校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//2. 返回错误信息
return Result.fail("手机号格式错误");
}
//3. 从Redis获取验证码并校验
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if(code == null || !code.equals(cacheCode)){
//不一致,返回错误信息
return Result.fail("验证码错误");
}
// 一致,根据手机号获取用户
User user = this.query().eq("phone", phone).one();
//5. 判断用户是否存在
if(user == null){
//6. 不存在,创建新用户
user = new User();
user.setPhone(phone); //设置phone
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); //设置随机昵称
this.save(user); // 存入数据库
}
//7. 用户存在,存入Redis缓存(不存入session作用域而是写入redis,解决session共享问题,因为redis本身就是共享的)
// 7.1 随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
// 7.2 将User对象转为HashMap存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.
create().
ignoreNullValue().
setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
// 7.3 存储
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
// 7.4 设置token过期时间(ttl)
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
}
拦截所有路径的拦截器,目的:刷新登录token
:
/**
* @author .29.
* @create 2023-11-27 21:23
* 拦截一切路径,目的是在访问不需要进行登录拦截的页面时,也能对用户token进行刷新
*/
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的token(前端将token存入请求头,请求头:authorization)
String token = request.getHeader("authorization");
if(StrUtil.isBlank(token)){
return true;
}
//2. 根据token获取redis中的用户
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
//3. 验证用户是否存在
if(userMap.isEmpty()){
return true;
}
//5. 将查询到的hash数据转换成userDTO对象(HashMap对象,目标转换对象,是否忽略转换过程的错误)
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//6. userDTO对象存入ThreadLocal(自定义工具类UserHolder,作用:创建并设置ThreadLocal)
UserHolder.saveUser(userDTO);
//7. 刷新token有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//8. 放行
return true;
}
}
登录拦截
:
/**
* TODO 登录 拦截器
* @author .29.
* @create 2023-11-26 16:37
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断用户是否存在
if(UserHolder.getUser() == null){
//不存在,拦截,设置401状态码
response.setStatus(401);
return false;
}
//用户存在、放行
return true;
}
}
MVC配置类,是拦截器生效
:
/**
* SpringMVC配置类,使拦截器生效
* @author .29.
* @create 2023-11-26 16:49
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1. 添加登录拦截器、同时设置无需拦截的路径
registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1); //order默认0,order值越大拦截器越后执行
//2. 添加token刷新的拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}