若将自定义的 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