Spring boot + thymeleaf + Shiro 会话过期返回登录界面片段之解决方案


解决方案的主导思想是:检测当会话过期时,判断是否为ajax请求,若是ajax请求,则将该url请求结果的状态置为401,并且不保存此次访问请求的url,当前端检测到访问结果为401时,跳转至登录界面,用户可顺利进行账户密码的输入并完成登录。


在此主导思想下将会遇到以下几个问题:


1.前端ajax请求完成后的统一处理问题;


2.后端会话过期的检测问题;


3.对封装后的ajax进行请求完成后的统一处理问题;


4.对临时添加的界面元素执行ajax请求的统一处理问题;


5.在spring boot框架下对shiro自定义的Formauthenticationfilter表单验证器的继承问题。


会话超时时执行相关操作,出现问题如下:

springboot jedis 过期时间 springboot shiro session过期重新登录_Shiro



会话超时时执行相关操作,问题解决目标如下:



springboot jedis 过期时间 springboot shiro session过期重新登录_Shiro_02




针对问题1,前端ajax请求后的统一处理问题解决如下:

/** 

 

  * 设置未来(全局)的AJAX请求默认选项 

 

  * 主要设置了AJAX请求遇到Session过期的情况 

 

  */ 

 

  $.ajaxSetup({ 

 

  complete: function(xhr,status) { 

 

  if(xhr.status == 401) { 

 

  var top = getTopWinow(); 

 

  top.location.href = '/login'; 

 

  } 

 

  } 

 

  }); 

 
  
 

  /** 

 

  * 在页面中任何嵌套层次的窗口中获取顶层窗口 

 

  * @return 当前页面的顶层窗口对象 

 

  */ 

 

  function getTopWinow(){ 

 

  var p = window; 

 

  while(p != p.parent){ 

 

  p = p.parent; 

 

  } 

 

  return p; 

 

  } 

 

  针对问题2,后端会话过期的检测问题解决如下: 

 

  解决思路为重写shiro表单过滤器FormAuthenticationFilter中的onAccessDenied方法,在该方法中除了实现原有的功能外,还需要增加判断,当shiro认证未通过的情况下,并且前端为ajax请求,则将该请求的请求状态置为401。 

 

  后端会话过期的检测及是否为ajax请求判断的主要代码如下: 

 

  public class XFormAuthenticationFilter extends FormAuthenticationFilter { 

 

  private static final Logger log = LoggerFactory.getLogger(XFormAuthenticationFilter.class); 

 
  
 

  public XFormAuthenticationFilter() { 

 

  setLoginUrl(DEFAULT_LOGIN_URL); 

 

  } 

 
 

 

  @Override 

 

  protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { 

 

  HttpServletRequest httpRequest = WebUtils.toHttp(request); 

 

  HttpServletResponse httpResponse = WebUtils.toHttp(response); 

 

  if (isLoginRequest(request, response)) { 

 

  if (isLoginSubmission(request, response)) { 

 

  if (log.isTraceEnabled()) { 

 

  log.trace("Login submission detected. Attempting to execute login."); 

 

  } 

 

  return executeLogin(request, response); 

 

  } else { 

 

  if (log.isTraceEnabled()) { 

 

  log.trace("Login page view."); 

 

  } 

 

  // allow them to see the login page ;) 

 

  return true; 

 

  } 

 

  } else { 

 

  if (log.isTraceEnabled()) { 

 

  log.trace("Attempting to access a path which requires authentication. Forwarding to the " 

 

  + "Authentication url [" + getLoginUrl() + "]"); 

 

  } 

 

  // 判断session里是否有用户信息 

 

  if (httpRequest.getHeader("X-Requested-With") != null 

 

  && httpRequest.getHeader("X-Requested-With").equalsIgnoreCase("XMLHttpRequest")) { 

 

  // 如果是ajax请求响应头会有,x-requested-with 

 

  httpResponse.sendError(HttpStatus.UNAUTHORIZED.value()); 

 
  
 

  //redirectToLogin(request, response); 

 

  } else { 

 

  redirectToLogin(request, response); 

 

  } 

 

  return false; 

 

  } 

 

  } 

 

  }

针对问题3,.对封装后的ajax进行请求完成后的统一处理问题的解决视具体情况而定。


1>对jquery-ujs扩展及ajax回调 UI渲染的ajax请求完成后的统一处理如下:

$("*[data-remote='true']").on('ajax:complete', function(xhr, status) { 

 

  if (status.status == 401) { 

 

  var top = getTopWinow(); 

 

  top.location.href = '/login'; 

 

  } 

 

  }); 

 

  2>对datatable封装后的ajax请求完成后的统一处理如下: 

 

  $.extend($.fn, { 

 

  cdataTable: function (setting) { 

 

  if (undefined == setting) { 

 

  setting = {}; 

 

  } 

 

  /** 

 

  * 设置未来(全局)的AJAX请求默认选项 

 

  * 主要设置了AJAX请求遇到Session过期的情况 

 

  */ 

 

  $.extend(setting.ajax, {"error": function (xhr, error, thrown) { 

 

  if(xhr.status == 401) { 

 

  var top = getTopWinow(); 

 

  top.location.href = '/login'; 

 

  } 

 

  } 

 

  }); 

 

  } 

 

  });

针对问题4,对临时添加的界面元素执行ajax请求的统一处理问题解决情况如下:


若本页面或页面片段在加载过程或相关程序执行过程中有临时界面元素生成,并要对此进行ajax请求后的结果进行统一处理,则需在该页面的页面上,加入以下代码:


$('form').on('ajax:complete', function(xhr, status) {//form表示执行ajax请求的界面元素 

 

  if (status.status == 401) { 

 

  var top = getTopWinow(); 

 

  top.location.href = '/login'; 

 

  } 

 

  });


针对问题5.对spring boot框架下对shiro自定义的Formauthenticationfilter表单验证器的继承问题的解决如下:


其解决方案按照shiro的java config配置即可,需要的一点是不要把继承Formauthenticationfilter表单验证器的XFormauthenticationfilter配置成bean,在ShiroFilterFactoryBean中直接使用即可,具体原因是若配置成bean,则使用anon白名单放过的url在shiro中可通过,但是会被spring boot容器拦截掉,而把XFormauthenticationfilter当做feibean的普通的shiro拦截器使用就不存在这个问题。具体代码如下:

@Autowired 

 

  @Bean(name = "shiroFilter") 

 

  public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securitymanager) { 

 

  ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 

 

  shiroFilterFactoryBean.setSecurityManager(securitymanager); 

 

  shiroFilterFactoryBean.setLoginUrl("/login"); 

 

  shiroFilterFactoryBean.setSuccessUrl("/"); 

 

  shiroFilterFactoryBean.setUnauthorizedUrl("/login"); 

 

  Map<String, Filter> filters = new HashMap<String, Filter>(); 

 

  filters.put("authc", new XFormAuthenticationFilter()); 

 

  shiroFilterFactoryBean.setFilters(filters); 

 

  filterChainDefinitionMap.put("/favicon.ico", "anon"); 

 

  filterChainDefinitionMap.put("/assets/**", "anon"); 

 

  filterChainDefinitionMap.put("/static/**", "anon"); 

 

  filterChainDefinitionMap.put("/user/service", "anon"); 

 

  filterChainDefinitionMap.put("/**", "authc"); 

 

  shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 

 

  return shiroFilterFactoryBean; 

 

  }

针对问题5,可能出现问题的具体描述如下,具体思想是不能让spring boot容器将自定义的shiro过滤器接管,即不能配置成bean的原因。