若将自定义的 MDCFilter 注册到 FilterRegistrationBean 中,而又在 MDCFilter 中使用了和 Shiro 相关的操作(如获取当前登录用户),此时会因为 MDCFilter 先于 SecurityManager 实例化导致出现 UnavailableSecurityManagerException 的异常,我们只需将 MDCFilter 注册到 Shiro 的过滤器链中即可解决这个问题。
文章目录
- 1.环境描述
- 2.问题描述
- 3.问题分析
- 4.问题解决
1.环境描述
- Springboot 3.1.5
- Shiro 1.12.0
2.问题描述
为了加入 trace 的能力,自定义了一个 MDCFilter,继承Filter,并交给 Spring 容器管理:
@Component
public class MDCFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        MDC.put(MdcConstant.TRACE_ID, IdUtil.fastUUID());
        MDC.put(MdcConstant.IP, IpUtils.getIpAddr(httpServletRequest));
        MDC.put(MdcConstant.USER, TokenUtils.isLogin() ? TokenUtils.getLoginUserId() : "anonymous");
        try {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } finally {
            MDC.clear();
        }
    }
}
同时在 FilterRegistrationBean 中注册该过滤器:
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<MyFilter> loggingFilter() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new MDCFilter());
        registrationBean.addUrlPatterns("/api/*");
        return registrationBean;
    }
}
本地启动正常,部署服务器后,发送请求就会出现如下异常 UnavailableSecurityManagerException(关键类路径脱敏处理):
2024-07-24 18:25:58.336 ERROR 1757379 --- [9c77170a-55f6-4256-b5be-590c5952443d] [                   ] [ 113.54.159.100] [nio-8188-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    175    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
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 *.TokenUtils.getLoginUser(TokenUtils.java:28)
	at *.isLogin(TokenUtils.java:37)
	at *.MDCFilter.doFilter(MDCFilter.java:38)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:833)
3.问题分析
简单测试后可以发现 SecurityManager 是在 Spring 容器中的,但是断点打到 SecurityManager 实例化的地方可以发现此时 MDCFilter 已经在 Shiro 的 filter 之前注册了,那么在请求到来的时候, MDCFilter 中调用 TokenUtils.getLoginUser,此时还未执行 shiro 的 filter,就会导致 UnavailableSecurityManagerException 的异常。
为什么 MDCFilter 会先于 SecurityManager 注册?
 因为 Spring 优先加载容器中的 Filter, Shiro 中的 Filter由 Shiro 负责完成加载过程。
为什么会出现本地部署和服务器部署加载顺序不一致的问题?
 这一点没有仔细去调试,猜测由于不同操作系统某些底层默认配置不一致,日后如果再遇到类似的问题可以深入研究下。
4.问题解决
解决问题就很简单了,我们把 MDCFilter 同样交给 Shiro 管理即可,注册在其过滤器链中:

ATFWUS 2024-07-25



















