一、错误描述
需求背景:新项目需要通过aop将请求日志打印出来,并且附上当前登录人的账号,系统认证使用 shiro 控制,想着直接通过 SecurityUtils.getSubject() 获取当前身份,但是很不幸的是,当用户没有登录时候直接报异常了,异常信息如下
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
二、网上各种解决方法
SecurityUtils.setSecurityManager(securityManager); 网上各种解决方案就是在初始化的时候将SecurityUtils 设置进去,但是我的是 xml 配置,而且这个类是抽象类,也不能设置属性;
2.1 方案1 springboot 解决
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
//在注册bean的时候,加入下面这句话,将securityManager放到SecurityUtils里面
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
2.2 web.xml 过滤器的顺序
很多说是因为 web.xml 里面有其他过滤器在前面,导致shiro的过滤器在后面不能处理,其实我是有些不信的,尝试调整顺序,果然还是不行;
三、最终解决方案
<dispatcher>ASYNC</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>加上error shiro 才能接管,因为shiro 的配置都是管理 /** 的url 但是有些错误的url没有接管,但是你又从session中获取,那么当然报错了
<!-- 配置shiro的代理过滤器 开始 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<servlet-name>dispatcherServlet</servlet-name>
<dispatcher>ASYNC</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- 配置shiro的代理过滤器 结束 -->
3.1 原因分析
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:627)
at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
……………………
这是我原本配置的配置
<!-- 配置shiro的代理过滤器 开始 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>
<!-- 配置shiro的代理过滤器 结束 -->
总结说明:你所请求的URL是不在Shiro所管辖范围的,而你又在你请求的这个URL后试图通过Shiro来获取Session,所以对Shiro来说“你不让我负责的事,为什么要跟我要结果”,我不能处理,我只能处理正确的url,那么我只能给你抛出异常了;
3.2 问题出现方式以及总结
当我配置了 404 的错误,定向到 404 的url 必现这个问题;
404很明显是一个ERROR,此前我没有添加 <code><dispatcher>ERROR</dispatcher>;</code>,而我在发生错误的时候又使用 SecurityUtils.getSubject().getSession() 来获取Session(我需要从会话中读取相关数据记录日志),所以被Shiro 所拒绝(抛出异常)。
--
如果你使用Shiro,并且没有配置 <dispatcher>ERROR</dispatcher> 。那么,你登录后,随便访问一个不存在的页面触发404,或者触发500这样的错误页。再回头看登录后的页面,登录状态就丢失了。因为这个时候sessionid 被重新生成覆盖了登录状态时的sessionid。
四、spring mvc 配置404 自定义页面
使用了 @ControllerAdvice 注解处理 @ExceptionHandler(NoHandlerFoundException.class) 但是根本捕获不到
@ControllerAdvice
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler(IllegalException.class)
public @ResponseBody LayRespView permissionException(IllegalException pe) {
return LayRespView.fail(pe.getErrCode(),pe.getMessage());
}
/**
* 400 - Bad Request
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public @ResponseBody LayRespView handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("参数解析失败:{}", e);
return LayRespView.fail("400","请求参数不合法");
}
/**
* 404 异常
* @param
* @return
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NoHandlerFoundException.class)
public void handlerFoundException(NoHandlerFoundException ex,HttpServletRequest request, HttpServletResponse response) throws IOException{
// 判断是否有登录
if (SecurityUtils.getSubject().getPrincipal() == null) {
try {
response.sendRedirect("/login/toLogin.fuiou");
return;
} catch (IOException e) {
log.info("重定向异常",e);
}
}
// 判断请求类型是否为json
if (request.getHeader("Content-Type").contains(MediaType.APPLICATION_JSON_VALUE)) {
StreamUtils.writeObjectToJson(response,LayRespView.fail("404","页面找不到,请检查url是否正确"));
return;
}
response.sendRedirect("/login/404.fuiou");
}
}
4.1 处理方案
自己在 web.xml 中定义errorcode 转向自己的servlet 处理
<error-page> <error-code>404</error-code> <location>/login/404.fuiou</location> </error-page>
4.2 定义 404 controller
/**
* 404 页面
* @return
*/
@RequestMapping("/404.fuiou")
public String notfound(HttpServletRequest request, HttpServletResponse response) {
// 判断是否有登录
if (!SecurityUtils.getSubject().isAuthenticated()) {
return "login";
}
// 判断请求类型是否为json
if (request.getHeader("Content-Type").contains(MediaType.APPLICATION_JSON_VALUE)) {
StreamUtils.writeObjectToJson(response,LayRespView.fail("404","页面找不到,请检查url是否正确"));
return null;
}
return "comm-error/404";
}