Spring Security自定义AuthenticationEntryPoint与AccessDeniedHandler失效问题
使用Spring Boot + Spring Security时,自定义了AuthenticationEntryPoint和AccessDeniedHandler,但在自定义Filter(WxAppletAuthenticationFilter)中抛出继承自AuthenticationException的CustomException时,这两个处理器并未生效,异常直接抛出到Tomcat层。
相关代码实现
CustomAccessDeniedHandler.java
@Slf4j public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter out = response.getWriter(); out.write(new ObjectMapper().writeValueAsString("no access")); out.flush(); out.close(); } }
CustomAuthenticationEntryPoint.java
@Slf4j public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.getWriter().write("login failed:" + authException.getMessage()); } }
CustomException.java
@Getter @AllArgsConstructor public class CustomException extends AuthenticationException { public CustomException(ResultEnum resultEnum) { super(resultEnum.getMsg()); } }
WebSecurityConfig.java
@EnableWebSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/error/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .accessDeniedHandler(customAccessDeniedHandler()) .authenticationEntryPoint(customAuthenticationEntryPoint()); http.addFilterAt(wxAppletAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Autowired private WxAppletAuthenticationManager wxAppletAuthenticationManager; @Bean public CustomAuthenticationEntryPoint customAuthenticationEntryPoint(){ return new CustomAuthenticationEntryPoint(); } @Bean public CustomAccessDeniedHandler customAccessDeniedHandler(){ return new CustomAccessDeniedHandler(); } @Bean public WxAppletAuthenticationFilter wxAppletAuthenticationFilter() { WxAppletAuthenticationFilter filter = new WxAppletAuthenticationFilter("/login"); filter.setAuthenticationManager(wxAppletAuthenticationManager); filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler()); return filter; } @Bean public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler() { return new CustomAuthenticationSuccessHandler(); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } }
错误栈信息
2022-11-17 00:52:48.263 ERROR 39496 --- [p-nio-80-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception com.tnxts.exception.CustomException: token无效 at com.tnxts.filter.WxAppletAuthenticationFilter.attemptAuthentication(WxAppletAuthenticationFilter.java:63) ~[classes/:na] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.4.jar:5.7.4] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.23.jar:5.3.23] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.68.jar:9.0.68] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
AuthenticationEntryPoint的作用场景:它仅处理未认证用户访问受保护资源的情况,也就是当请求没有经过认证就试图访问需要权限的接口时才会触发。而你的异常是在认证过程中(WxAppletAuthenticationFilter的attemptAuthentication方法)抛出的,不属于这个场景。AbstractAuthenticationProcessingFilter的异常处理逻辑:查看该类的doFilter方法可知,只有当AuthenticationManager的authenticate方法抛出AuthenticationException时,才会调用AuthenticationFailureHandler处理异常;如果是在attemptAuthentication方法中直接抛出异常,且没有设置AuthenticationFailureHandler,异常会直接向上抛出,不会触发任何Spring Security的异常处理器,最终由Tomcat的错误处理器处理。你的
WxAppletAuthenticationFilter只设置了AuthenticationSuccessHandler,没有配置AuthenticationFailureHandler,所以抛出的CustomException无法被正确捕获处理。
为自定义的WxAppletAuthenticationFilter添加AuthenticationFailureHandler,专门处理认证过程中抛出的AuthenticationException(包括你的CustomException)。
代码修改示例
1. 实现自定义AuthenticationFailureHandler
@Slf4j public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter out = response.getWriter(); // 这里可以根据异常类型返回不同信息,比如你的CustomException if (exception instanceof CustomException) { out.write(new ObjectMapper().writeValueAsString("登录失败:" + exception.getMessage())); } else { out.write(new ObjectMapper().writeValueAsString("登录失败:" + exception.getMessage())); } out.flush(); out.close(); } }
2. 在WebSecurityConfig中注册并配置到Filter
@EnableWebSecurity public class WebSecurityConfig { // ... 其他代码不变 ... @Bean public CustomAuthenticationFailureHandler customAuthenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } @Bean public WxAppletAuthenticationFilter wxAppletAuthenticationFilter() { WxAppletAuthenticationFilter filter = new WxAppletAuthenticationFilter("/login"); filter.setAuthenticationManager(wxAppletAuthenticationManager); filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler()); // 添加这一行,设置失败处理器 filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler()); return filter; } // ... 其他代码不变 ... }
内容的提问来源于stack exchange,提问作者Tnxts

