遇到一个 Shiro 中反复调用 doGetAuthenticationInfo 导致异常没有被成功处理的问题,经过一些源码调试,发现了问题的所在,只需在继承 BasicHttpAuthenticationFilter 的类中重写 onAccessDenied 方法即可。
文章目录
- 1.问题环境
- 2.问题描述
- 3.问题调试
- 4.问题解决
1.问题环境
- Springboot 3.1.5
- Shiro 1.12.0
2.问题描述
在自己的 MyReal
类中,重写了 doGetAuthenticationInfo
方法:
@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {String token = (String) auth.getCredentials();if (token == null) {HttpServletRequest req = SpringContextUtils.getHttpServletRequest();throw new AuthenticationException("token为空!");}// 校验token有效性LoginUser loginUser = this.checkUserTokenIsEffect(token);return new SimpleAuthenticationInfo(loginUser, token, getName());}
在继承 BasicHttpAuthentication Filter
的类JwtFilter
中重写了isAccessAllowed
方法和executeLogin
方法:
@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {executeLogin(request, response);return true;} catch (Exception e) {JwtUtil.responseError(response,401,CommonConstant.TOKEN_IS_INVALID_MSG);return false;}}
现在的问题是:
当 token 为空时,抛出了new AuthenticationException("token为空!");
的异常,这个异常被JwtFilter
中的isAccessAllowed
捕获了,并且提交了 response。
但是神奇的是,isAccessAllowed
方法返回 false 后,又调用了 MyReal
类中的doGetAuthenticationInfo
方法,这个方法继续抛出异常,此时这个授权异常没有被正常处理,最后进入了全局异常处理的handler
中,而此时 response 已经被提交了,于是就又导致了其它的异常。如下所示:(关键类路径被脱敏了)
org.apache.shiro.authc.AuthenticationException: token为空!at *.ShiroRealm.doGetAuthenticationInfo(ShiroRealm.java:90)at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:571)at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)at *.JwtFilter.executeLogin(JwtFilter.java:76)at org.apache.shiro.web.filter.authc.HttpAuthenticationFilter.onAccessDenied(HttpAuthenticationFilter.java:230)at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:223)at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:198)at *.JwtFilter.preHandle(JwtFilter.java:117)at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458)at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373)at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)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:842)org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.util.LinkedHashMap] with preset Content-Type 'text/json;charset=UTF-8'at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:319)at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:245)at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)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.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)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.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)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.ApplicationDispatcher.invoke(ApplicationDispatcher.java:642)at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:520)at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:463)at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:343)at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:222)at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:308)at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:149)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:842)
2024-07-24 16:56:00.079 WARN 71569 --- [ ] [ ] [ ] [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver 419 : Failure in @ExceptionHandler *.GlobalExceptionHandler#handleException(Exception)org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class *.Result] with preset Content-Type 'text/json;charset=UTF-8'at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:319)at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:194)at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136)at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:413)at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:74)at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:141)at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:80)at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1341)at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1152)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1098)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)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.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)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.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)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.ApplicationDispatcher.invoke(ApplicationDispatcher.java:642)at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:520)at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:463)at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:343)at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:222)at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:308)at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:149)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:842)2024-07-24 16:56:00.083 WARN 71569 --- [ ] [ ] [ ] [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver 207 : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.util.LinkedHashMap] with preset Content-Type 'text/json;charset=UTF-8']
3.问题调试
关键位置打上断点:
经过调试可以发现,第一次的异常被捕获处理后,isAccessAllowed
方法返回 false ,然后进入AccessControlFilter
类的onPreHandle
方法中:
继续调试,可以发现又进入了onAccessDenied
方法中,而其中又会调用executeLogin
方法,又重复进入了MyReal
类中的doGetAuthenticationInfo
方法,此时再次抛出异常,这个异常已经不能被正确处理了。
4.问题解决
可以发现,问题的根本在于onAccessDenied
方法中会调用executeLogin
方法,又重复进入了MyReal
类中的doGetAuthenticationInfo
方法。我们只需重写onAccessDenied
方法即可解决此问题。
在在继承 BasicHttpAuthenticationFilter
的类JwtFilter
中重写onAccessDenied
方法:
@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {this.sendChallenge(request, response);return false;}
至此问题完美解决!
ATFWUS 2024-07-24