org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling

问题描述

由于最近项目中需要服务端往客户端推送消息,于是使用了SSE(Server-Sent Events)技术。在服务端发送完消息调用emitter.complete()之后,出现了以下错误信息:

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:626)
 at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
 at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSubject(ShiroHttpServletRequest.java:89)
 at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSession(ShiroHttpServletRequest.java:154)
 at org.springframework.web.util.WebUtils.getSessionId(WebUtils.java:288)
 at org.springframework.web.servlet.FrameworkServlet.publishRequestHandledEvent(FrameworkServlet.java:1077)
 at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
 at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
 at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
 at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)
 at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:633)
 at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:603)
 ......

问题分析

该项目使用了Shiro。在Shiro中,ShiroHttpServletRequest 类覆盖了 HttpServletRequest 的 getSession 方法。从错误信息来看调用代码无法访问到SecurityManager,既没有绑定到线程上下文,也没有作为虚拟机静态单例存在。

因为代码中在接到请求之后从线程池中获取了新的线程B进行推送消息,所以第一反应是shiro的Subject信息没有传递过去,经过测试发现在线程B中获取session是不会报错的。

经过查找资料得知Java EE 自 Servlet 3.0 开始对 Servlet 和 Filter 提供了异步支持。如果 Servlet 和 Filter 在处理请求时可能会发生阻塞,可以将阻塞请求线程的操作分配到异步线程,然后将处理请求的线程归还到 Servlet 容器中的线程池,而不产生响应,当异步线程中的操作完成,异步线程可以直接产生响应或将请求重新分派到容器中的 Servlet 处理。

Spring MVC 3.2 引入了基于Servlet 3的异步请求处理。控制器方法现在可以返回一个java.util.concurrent.Callable对象,而不是像通常那样返回一个值,并在一个由Spring MVC管理的线程中产生返回值。与此同时,主Servlet容器线程会退出并释放,允许它处理其他请求。Spring MVC通过TaskExecutor的帮助在单独的线程中调用Callable,当Callable返回时,请求会被分派回Servlet容器,以使用Callable返回的值恢复处理。以下是一个这样的控制器方法的示例:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}

另一个选择是让控制器方法返回一个DeferredResult的实例。在这种情况下,返回值也将从任何线程产生,即不是由Spring MVC管理的线程。例如,结果可能是为了响应某些外部事件而产生的,如JMS消息、定时任务等。以下是一个这样的控制器方法的示例:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}
// In some other thread...
deferredResult.setResult(data);

除了上述两种返回值类型,还有一些其他类型。本项目中控制器返回的SseEmitter是ResponseBodyEmitter的子类,提供了对服务器发送事件(Server-Sent Events,简称SSE)的支持。程序中发送完消息调用SseEmitter的complete方法后,Spring MVC将请求分派回Servlet容器。DispatcherServlet再次被调用,处理随着异步产生的结果而恢复。此时的异步请求的线程和原始请求不是同一个线程,处理过程中获取session时调用了ShiroHttpServletRequest的getSession方法,没有找到可用的SecurityManager进而抛错了。

熟悉shiro的朋友们应该知道,shiro是作为servlet的filter起作用的,一个请求被shiro拦截之后,进行一系列处理,后续在该请求的线程中才能访问到SecurityManager。所以前面提到异步产生的结果恢复的请求并没有被shiro拦截到。就本项目而言,配置shiro过滤器的规则如下:

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <async-supported>true</async-supported>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

乍一看所有URL都匹配了,async-supported也开启了,应该没有问题。其实这里还需要配置dispatcher,如果不写默认是过滤REQUEST请求,参考https://shiro.apache.org/webapp-tutorial.html、https://shiro.apache.org/web.html#configuration 中的内容,<filter-mapping>声明确保所有请求类型都由ShiroFilter处理。通常,filter-mapping声明不会指定<dispatcher>元素,但Shiro需要它们全部定义,这样它才能过滤可能针对Web应用程序执行的所有不同类型的请求。

所以上面filter-mapping中的配置应该设为:

       <filter-name>shiroFilter</filter-name>
       <url-pattern>/*</url-pattern>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>ASYNC</dispatcher>

这样就不会出现找不到SecurityManager的错误了。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值