spring boot 2.x + shiro + redis前后端分离,无权限访问时session会存入redis的问题解决

前言

继上篇文章 spring boot 2.x + shiro + redis实现前后端分离的项目 后有不少网友反应当用户无权限访问的时候,redis还是会多一条session存入的记录,后来证实发现确实如此,下面我们就来看看如何解决这个问题吧!

原因

首先我们来了解一下为什么会在无权限访问的时候会产生session?原因很简单,我们在ShiroConfig配置类中配置了未授权时跳转的页面地址,当我们携带无效的token访问后端系统时,shiro的authc默认拦截器会将请求重定向到未授权页面,而拦截器在重定向之前创建了一个session对象,我们来简单看一下FormAuthenticationFilter拦截器的onAccessDenied方法:
spring boot 2.x + shiro + redis前后端分离,无权限访问时session会存入redis的问题解决_第1张图片
我们在进入saveRequestAndRedirectToLogin方法看下
在这里插入图片描述
经打断点查看后,发现在执行saveRequest方法后,redis里面就产生了一条session的记录,我们再进入saveRequest方法看一下
在这里插入图片描述
spring boot 2.x + shiro + redis前后端分离,无权限访问时session会存入redis的问题解决_第2张图片
如果有兴趣的同学可以继续进入subject.getSession()方法中查看是如何创建的session的,这边我就不继续拓展下去了。

解决

解决的思路有几种,我这边先提供两种方式

方式一:继承FormAuthenticationFilter拦截器,重写onAccessDenied方法;
方式二:ShiroConfig放弃集成redisSessionDao,手动的去保存、读取和删除session;

方式一的优点主要是修改简单,很容易的解决上面的问题,但缺点是对于不知道源码逻辑的朋友就是一个黑盒,可能会产生其他的问题;
方式二的缺点主要是代码改造量比较大,但优点是可以自由控制,而且redis中存储的信息也可以是非序列化的内容,增加可读性和可修改性;

博主这边只贴出第一种方式的代码,有兴趣的朋友可以自己尝试第二种方法。
首先我们自己创建AuthcShiroFilter类继承FormAuthenticationFilter并重写onAccessDenied方法,具体思路就是:原本应该重定向的到未授权方法,这边不再实现重定向的逻辑,直接返回前端对应的信息即可,如下

public class AuthcShiroFilter extends FormAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            // option请求处理
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;
            if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
                resp.setStatus(HttpStatus.OK.value());
                return true;
            }

            // 取消重定向,直接返回结果
            returnTokenInvalid((HttpServletRequest)request, (HttpServletResponse)response);
            return false;
        }
    }

    /**
     * 替代shiro重定向
     *
     * @param req
     * @param resp
     * @throws IOException
     */
    private void returnTokenInvalid(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        resp.setContentType("application/json; charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        Writer out = new BufferedWriter(new OutputStreamWriter(resp.getOutputStream()));
        out.write(JSONObject.toJSONString(new Result(ResultStatusCode.INVALID_TOKEN)));
        out.flush();
        out.close();
    }
}

最后在ShiroConfig配置类中重新指定authc的拦截器即可

@Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 由于已经重写了authc的拦截器,此处设置的loginUrl和unauthorizedUrl已经没有用了
        // 没有登陆的用户只能访问登陆页面,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
        //shiroFilterFactoryBean.setLoginUrl("/common/unauth");
        // 登录成功后要跳转的链接
        //shiroFilterFactoryBean.setSuccessUrl("/auth/index");
        // 未授权界面;
        //shiroFilterFactoryBean.setUnauthorizedUrl("common/unauth");

        //自定义拦截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        //自定义authc访问拦截器
        filtersMap.put("authc", new AuthcShiroFilter());
        //限制同一帐号同时在线的个数。
        filtersMap.put("kickout", kickoutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);

        // 权限控制map.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 公共请求
        filterChainDefinitionMap.put("/common/**", "anon");
        // 静态资源
        filterChainDefinitionMap.put("/static/**", "anon");
        // 登录方法
        filterChainDefinitionMap.put("/admin/*ogin*", "anon"); // 表示可以匿名访问

        //此处需要添加一个kickout,上面添加的自定义拦截器才能生效
        filterChainDefinitionMap.put("/admin/**", "authc,kickout");// 表示需要认证才可以访问
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

至此已经全部ok,感谢各位!全部的源码请访问:源码地址

你可能感兴趣的:(shiro)