springboot2.x整合shiro使用自定义过滤器解决session过期跳转login问题

1、问题出现原因:
问题一:后台springboot+shiro,前端使用iframe版layui。由于是iframe版是前端页面跳转不经过后台,session过期不会引起重定向到login。部分跳转页面session过期后可以重定向。
问题二:大量button事件触发走的ajax,返回的是login页面html形式的json串,解析有错误,显示parseerror。那么多ajax一个一个写错误重定向太麻烦不现实。

2、解决方法:使用shiro自定义过滤器+前端设置全局的ajax过滤。

3、首先,shiro基本的配置不说了,网上找很简单的。
我的自定义过滤器

public class LoginFilter  extends FormAuthenticationFilter {
    private static final String[] filter = {"/login", "/index","/tologin", "/icon", "/image", "/layuimini-2", "/logout"};

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest hsRequest = (HttpServletRequest) request;
        HttpServletResponse hsResponse = (HttpServletResponse) response;
        String url = hsRequest.getRequestURI();

        // 不需要过滤的请求地址以及文件
        for (String str : filter) {
            if (url.contains(str) || url.equalsIgnoreCase("/")){
                return true;
            }
        }
		//这里是获取用户登录信息
        Subject subject = getSubject(request, response);
        // 如果没有获取到用户信息,将退出到登陆界面
        if (null == subject.getPrincipal()) {
            // 从请求头部获取请求方式,ajax是X-Requested-With
            String requestedWith = hsRequest.getHeader("X-Requested-With");
            // 如果是Ajax返回指定数据
            if (StringUtils.isNotEmpty(requestedWith) && StringUtils.equals(requestedWith, "XMLHttpRequest")) {
                // 将要重定向WEB地址(项目地址),可以拼链接,我这里是直接固定/tologin
//                String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + hsRequest.getContextPath() + "/";
				//可以通过code403判断是否重定向,也可以自定义一个属性指定是session超时的重定向
                hsResponse.setHeader("sessionstatus", "TIMEOUT");					// 返回特定数据(头部信息)
                hsResponse.setHeader("content_path", "/tologin");			// 返回特定数据(首页登陆地址)
                hsResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);				// 403 禁止:状态代码(403),指示服务器了解请求,但拒绝履行。
                return false;
            } else {	// 不是Ajax进行重定向处理
                log.info( url + "重定向到了登录界面");
                hsResponse.sendRedirect("/tologin");		// 重定向到登陆界面
                return false;
            }
        }
        return true;
    }
}

要注意的几点是:

shiro官方给的默认过滤器

Filter NameClass
anonorg.apache.shiro.web.filter.authc.AnonymousFilter
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
authcBearerorg.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter
logoutorg.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreationorg.apache.shiro.web.filter.session.NoSessionCreationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter
sslorg.apache.shiro.web.filter.authz.SslFilter
userorg.apache.shiro.web.filter.authc.UserFilter

如果是跟用户权限有关的用user,ssl有关的用ssl的过滤器即可。这里我用的是FormAuthenticationFilter,因为我的/**权限过滤用的是authc。

然后在securityManager里加上自定义的过滤器。

 @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/tologin");
        //  登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权时跳转的提示界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/404");


        // 本篇重点看这里
        //由于下面的filterMap.put("/**","authc")所以前面用的authc的过滤器
    	//这里切记用new LoginFilter()交给子过滤器执行,不要用bean,那会给spring管理然后走两遍filter,导致很多问题。
        Map<String, Filter> myfilter = new LinkedHashMap<>(20);
        myfilter.put("LoginFilter",new LoginFilter());
        shiroFilterFactoryBean.setFilters(myfilter);
		//本篇重点看这里
		
		
        Map<String, String> filterMap = new LinkedHashMap<String, String>();
        filterMap.put("/icon/**","anon");
        filterMap.put("/image/**","anon");
        filterMap.put("/js/**","anon");
        filterMap.put("/layuimini-2/**","anon");
        filterMap.put("/login","anon");
        filterMap.put("/tologin","anon");
        filterMap.put("/logout","logout");
        filterMap.put("/**","authc");//过滤链定义,从上向下顺序执行,一般将/**放在最为下边
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

最后是前端ajax的过滤
写成一个common.js,然后每个页面引用即可。你框架不同的jquery就多写一个。
比如标准jquery和layui的jquery,我这里写了两个。

if(typeof jQuery != 'undefined') {
    //重写ajax
    $.ajaxSetup( {
        //设置ajax请求结束后的执行动作
        complete :
            function(XMLHttpRequest, textStatus) {
                // 通过XMLHttpRequest取得响应头,sessionstatus
                var sessionstatus = XMLHttpRequest.getResponseHeader("sessionstatus");
                if (sessionstatus == "TIMEOUT") {
                    var win = window;
                    while (win != win.top) {
                        win = win.top;
                    }
                    win.location.href = XMLHttpRequest.getResponseHeader("content_path");
                }
            }
    });
}

// 如果使用了layui框架,也必须要重写layui的ajax(用什么框架自行查找对应解决方案)
if(typeof layui != 'undefined') {
    layui.use(['form','laydate'],function(){
        var form = layui.form,
            laydate = layui.laydate,//日期
            $ = layui.jquery;
        $(".layui-date").each(function(){
            var id = $(this).attr("id");
            laydate.render({
                elem:'#'+id,
                format:'yyyy/MM/dd',
                max:0
            });
        });
        //重写ajax(layui)
        $.ajaxSetup({
            //设置ajax请求结束后的执行动作
            complete :
                function(XMLHttpRequest, textStatus) {
                    // 通过XMLHttpRequest取得响应头,sessionstatus
                    var sessionstatus = XMLHttpRequest.getResponseHeader("sessionstatus");
                    if (sessionstatus == "TIMEOUT") {
                        var win = window;
                        while (win != win.top) {
                            win = win.top;
                        }
                        win.location.href = XMLHttpRequest.getResponseHeader("content_path");
                    }
                }
        });
    });
}

最后的效果就是session到期了,页面跳转会自动重定向到login,然后如果是ajax的json格式请求也会后端拦截重定向,前端全局的ajax重定向到login。
大概流程就是:你的请求 --> 后端过滤器(放行/重定向)–> 后端业务返回(json/视图) --> 前端全局ajax过滤(放行/重定向) --> 你的请求的ajax的success方法/跳转页面。

网上的其他资源很多因为版本和时间老旧原因不好使,多研究官网的地址,上面有各种的逻辑原理,配合其他文章理解其中的思路就可以自定义解决一些问题了。

比如我这里是找到文章说可以设置自定义过滤器重定向session过期,然后又看到了说前端可以用websocket重定向(但是我不会,下一步研究研究websocket),于是又找到了说可以设置ajax全局过滤,如果是重定向给你拦截了重定向到那个页面,最后两个解决思路一相加就解决了。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值