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官方给的默认过滤器
如果是跟用户权限有关的用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全局过滤,如果是重定向给你拦截了重定向到那个页面,最后两个解决思路一相加就解决了。