Shiro中的session共享问题(如果再也见不到你,祝你早安,午安,晚安。)

1.shiro中session的共享问题

 当我们通过nginx反向代理到我们的集群后,如果用户再进行登录,用户的session会存储在第一次负载均衡到的服务器上,但是如果我们调用其中的一些功能或者说权限后,用户在另外一台服务器上并没有session信息,就会导致提示未登录问题,需要用户再次进行登录。

但是我们不可能让用户登录很多次,这对用户的体验来说是非常不友好的,因此解决shiro安全框架中的session问题是非常有必要的。

2.如何解决session共享问题

这里使用的是第三方的框架crazycake

        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.3.1</version>
        </dependency>

2.1修改shiro的配置类

DefaultWebSessionManager类主要用于获取请求头中的JSESSIONID的值

然后再通过RedisSessionDao从redis中查询对应的Key,如果存在,则认为当前用户已登录

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        //redis管理
        securityManager.setCacheManager(redisCacheManager());
        //设置session管理
        securityManager.setSessionManager(defaultWebSessionManager());
        return securityManager;
    }

    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(){
        MyWebSessionManager defaultWebSessionManager = new MyWebSessionManager();
        defaultWebSessionManager.setSessionDAO(sessionDAO());
        return defaultWebSessionManager;
    }

    @Bean
    public SessionDAO sessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("127.0.0.1:6379");
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }

2.2解决前端不支持cookie

默认DefaultWebSessionManager它只接受Cookie中存储的JSESSIONID.

查询发现redis中不存在对应的key

如何解决:

客户发送请求时,再请求头中携带sessionId, 然后重写DefaultWebSessionManager中getSessionId()的方法。

2.3JSESSIONID放入请求头

修改登录的接口

 修改前端登录方法

 修改main.js文件

 2.4 重写DefaultWebSessionManager的方法

public class MyWebSessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION = "token";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //获取请求头中名称为token的内容
        String id = WebUtils.toHttp(request).getHeader("token");
        if (!StringUtils.isEmpty(id)) { //如果存在该token
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //从cookie中获取sessionId.
            return super.getSessionId(request, response);
        }
    }
}

修改shiro配置类:

    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(){
        //使用自定义的defaultWebSessionManager
        MyWebSessionManager defaultWebSessionManager = new MyWebSessionManager();
        defaultWebSessionManager.setSessionDAO(sessionDAO());
        return defaultWebSessionManager;
    }

修改shiroFilter过滤器

我们发现跨域请求,会发送两个请求:第一个OPTIONS请求,第二个请求是真实的请求。

OPTIONS请求:先头部队。

所以我们对OPTIONS请求都要放行

public class MyFilter extends FormAuthenticationFilter {
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //响应数据的中文乱码
        response.setContentType("application/json;charset=utf-8");

        //返回的json数据
        R<Object> error = R.error("登录了吗你?????");

        //转换为json字符串返回
        String string = JSON.toJSONString(error);

        //响应前端
        PrintWriter writer = response.getWriter();
        writer.print(string);

        writer.flush();
        writer.close();
        return false;

    }
    //会发送两个请求:第一个OPTIONS请求,第二个请求是真实的请求。
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        //获取请求方式
        String method = req.getMethod();
        if("OPTIONS".equals(method)){
            return true;
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }
}

3.前端前置路由守卫

//前置路由守卫设置
router.beforeEach((to,from,next) => {
  //获取当前路由路径
  var path = to.path;
  if(path == "/login"){
    return next();//继续放行
  }
  //判断是否登录
  var token = sessionStorage.getItem("token");
  if(token){
    return next();
  }
  //返回到主界面登录
  return next("/login")
})

4.防止恶意重复登录

@Controller
@ResponseBody
public class LoginController {

    @PostMapping("/login")
    public R login(@RequestBody LoginVo loginVo){
        Subject subject = SecurityUtils.getSubject();

        //判断是否已经登录 防止重复登录查询数据库
        if (subject.isAuthenticated()) {
            return R.success(subject.getSession().getId().toString());
        }

        //封装用户登录信息
        UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
        try {
       
            //进行登录认证
            subject.login(token);
            return R.success(subject.getSession().getId().toString());//返回sessionId给前端
        }catch (Exception e){
            e.printStackTrace();
            return R.error("登录失败");
        }
    }
}

5.退出登录

    @GetMapping("/logout")
    public R logout(){
        //清除redis缓存
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return R.success("退出成功");
    }

前端退出:

    //退出登录
    logout(){
      this.$http.get("http://localhost:8088/user/logout").then(result => {
        if(result.data.code == 1){
          this.$message.success("退出成功")
          //清除token
          sessionStorage.clear()
          //返回登录界面
          this.$router.push("/login")
        }
      })

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值