集群session的共享问题

基于redis实现共享session登录

1.集群session共享的问题

session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失问题

替代方案应该满足:

数据共享

内存存储

key、value结构

 

2.基于redis实现session共享登录

 

需要生成token令牌,通过token的值去确定哪个用户做出请求

将token作为key用户信息作为值存储到redis中,利用的模板时StringRedisTemplate这样不仅可以节省服务器的空间还增加可读性

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    //1.校验手机号的格式
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        //2.不一致直接报错
        return Result.fail("手机号错误");
    }
    //3.比较验证码
    String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
    String code = loginForm.getCode();
    if(session==null || !cacheCode.equals(code)){
        //4.不一致直接报错
        return Result.fail("错误信息");
    }
    //5.根据手机号查询用户
    LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
    query.eq(User::getPhone,loginForm.getPhone());
    User user = this.getOne(query);
    if(user==null){
        //6.不存在直接创建新用户保存到数据库中
        user=createUserWithPhone(loginForm.getPhone());
    }
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    //7.最终将用户信息保存到redis中  使用UserDTO保护用户的隐私
    //7.1生成token
    String tokenKey = UUID.randomUUID().toString();
    /*
    * 当用户第一次登录后,服务器生成一个token并将此token返回给客户端,
    * 以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
    * */
    //7.2 将user转为hash存储
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create()
    .setIgnoreNullValue(true)
    .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
    //7.3存储
    stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+tokenKey,userMap);
    //7.4设置存储的生命周期
    /*
    * 这里设置过30min会自动从redis中剔除 但我们想要的效果是30min没有用到token时剔除
    * 这时我们需要去拦截器里面设置并且刷新token
    * */
    stringRedisTemplate.expire(LOGIN_USER_KEY+tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
    //8.返回给客户端
    return Result.ok(tokenKey);
}

拦截器的设定之前设置的拦截器放行了部分页面,如果我们只访问那种部分页面时拦截器直接放行导致不能够刷新redis中的数据,现在设定两个拦截器,一个负责专门刷新redis的生命周期,另一个负责拦截

//注册拦截器  及其相关配置
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    //添加拦截器
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //order的值越小 执行的优先级越高
        //登录拦截器
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot",
                "/user/code",
                "/user/login"
        ).order(1);
        //刷新拦截器  拦截所有
        registry.addInterceptor(new ReFreshTokenInterceptor(redisTemplate)).order(0).addPathPatterns("/**");
​
    }
}
​
@SuppressWarnings("all")
//创建拦截器
public class ReFreshTokenInterceptor implements HandlerInterceptor {
    //有注册拦截器注入bean
    private StringRedisTemplate stringRedisTemplate;
​
    public ReFreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
​
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取token
        String token = request.getHeader("authorization");
        if(StringUtils.isEmpty(token)){
            return true;
        }
        //2.通过token从redis中拿到用户信息
        Map<Object, Object> user = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY+token);
        if(user.isEmpty()){
​
            return true;
        }
        //4.存在  将Map对象转为UserDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(user, new UserDTO(), false);
        //5.存储到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //6.设置token的刷新时间
        stringRedisTemplate.expire(LOGIN_CODE_KEY+token,LOGIN_USER_TTL, TimeUnit.MINUTES);
        //5.放行
        return true;
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //避免造成内存泄露
        UserHolder.removeUser();
    }
}
public class LoginInterceptor implements HandlerInterceptor {
    //有注册拦截器注入bean
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //1.只需要判断ThreadLocal中有没有用户信息
        if(UserHolder.getUser()==null){
            //响应未授权的状态信息
            response.setStatus(401);
            //拦截
            return false;
        }
        //放行
        return true;
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //避免造成内存泄露
        UserHolder.removeUser();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值