spring 4 中自定义 ExceptionTranslationFilter 来处理 REST AuthenticationException

Posted

技术标签:

【中文标题】spring 4 中自定义 ExceptionTranslationFilter 来处理 REST AuthenticationException【英文标题】:Custom ExceptionTranslationFilter in spring 4 to handle REST AuthenticationException 【发布时间】:2015-01-27 08:06:19 【问题描述】:

我有一个由 spring security 保护的 spring MVC RESTful 应用程序。客户端是 Angular JS。 但是我的登录和注销页面是普通的 jsp,我做基于表单的登录和注销。成功完成完全身份验证后,我加载了我的安全页面(它使用 anlgularJS 和 RESTful api)。

在调用 REST api 时,我需要帮助处理 AuthenticationException 和 AccessDeniedException。 我扩展了 ResponseEntityExceptionHandler 并且能够捕获 AccessDEniedException 并抛出一个 json 响应。 我知道要处理 AuthenticationException(所以我可以在休息完整请求的情况下停止 302 重定向)我需要扩展 ExceptionTranslationFilter。 我发现的大多数示例都使用 XML 配置。但是可以在 Java 配置中做吗?

我确实编写了一个组件来扩展 ExceptionTranslationFilter 并创建了一个自定义 AuthenticationEntryPoint,但我不确定如何将它注入到我的 ExceptionTranslationFilter 中

我收到一个错误 原因:java.lang.IllegalArgumentException:必须指定 authenticationEntryPoint 我尝试添加一个构造函数,但找不到它的方法。 任何解决问题的帮助表示赞赏。

MyAuthenticationEntryPoint

@Service
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint 

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2)
            throws IOException, ServletException 
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                "Unauthorized.");

    


我的异常翻译过滤器

@Component
public class RestExceptionTranslationFilter extends ExceptionTranslationFilter 
    public static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionTranslationFilter.class);

    @Override
    protected void sendStartAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, AuthenticationException reason)
            throws ServletException, IOException 

            boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));

        if (isAjax) 

            String jsonObject = "\"message\":\"Please login first.\"," + "\"access-denied\":true,\"cause\":\"AUTHENTICATION_FAILURE\"";
                String contentType = "application/json";
                resp.setContentType(contentType);
                PrintWriter out = resp.getWriter();
                out.print(jsonObject);
                out.flush();
                out.close();
                return;
            

        super.sendStartAuthentication(req, resp, chain, reason);
    


安全配置

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    public static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);

    @Autowired
    UserDetailsRepository userDetailsRepository;

    /**
     * In Memory Authentication
     * @param auth
     * @throws Exception
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsRepository);
    


    @Override
    public void configure(WebSecurity web) throws Exception 
        web.ignoring().antMatchers("/resources/**");

    

    @Override
    public void configure(HttpSecurity http) throws Exception 
        LOGGER.debug("Configuring Spring Security HttpSecurity...");
        http.authorizeRequests().antMatchers("/api/**").authenticated();
        http.authorizeRequests().antMatchers("/app.html").authenticated();
        http.authorizeRequests().antMatchers("/login**").permitAll();
        http.authorizeRequests().and().formLogin().loginProcessingUrl("/login").loginPage("/login.jsp").defaultSuccessUrl("/app.html").and()
            .authorizeRequests().and().logout().logoutUrl("/logout").logoutSuccessUrl("/logout.html").permitAll();
        http.csrf().disable();
    

    @Bean
    public static ExceptionTranslationFilter exceptionTranslationFilter() 
        RestExceptionTranslationFilter exceptionTranslationFilter = new RestExceptionTranslationFilter(new RestAuthenticationEntryPoint());
        RestAccessDeniedHandler accessDeniedHandlerImpl = new RestAccessDeniedHandler();
        exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandlerImpl);
        exceptionTranslationFilter.afterPropertiesSet();
        return exceptionTranslationFilter;
    

错误日志

2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 6 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 8 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2015-01-14_23:10:17.270 DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - doFilter -
                Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 9 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
2015-01-14_23:10:17.270 DEBUG o.s.s.w.s.SessionManagementFilter - doFilter -
                Requested session ID CC2CA43BAE60F06F2431C9280A74081F is invalid.
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 10 of 12 in additional filter chain; firing Filter: 'RestExceptionTranslationFilter'
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2015-01-14_23:10:17.270 DEBUG o.s.security.web.FilterChainProxy - doFilter -
                /org/hmie/fms/sy/myAccount.html at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2015-01-14_23:10:17.270 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/login.jsp'
2015-01-14_23:10:17.270 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/login'
2015-01-14_23:10:17.286 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/logout.html'
2015-01-14_23:10:17.286 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/ess/partials/alertpopup.html'
2015-01-14_23:10:17.286 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/partials/alertpopup.html'
2015-01-14_23:10:17.287 DEBUG o.s.s.w.u.m.AntPathRequestMatcher - matches -
                Checking match of request : '/org/hmie/fms/sy/myaccount.html'; against '/fms/**'
2015-01-14_23:10:17.287 DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - beforeInvocation -
                Secure object: FilterInvocation: URL: /org/hmie/fms/sy/myAccount.html; Attributes: [authenticated]
2015-01-14_23:10:17.287 DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - authenticateIfRequired -
                Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2015-01-14_23:10:17.287 DEBUG o.s.s.access.vote.AffirmativeBased - decide -
                Voter: org.springframework.security.web.access.expression.WebExpressionVoter@5fcd184d, returned: -1
2015-01-14_23:10:17.298 DEBUG o.s.s.w.a.ExceptionTranslationFilter - handleSpringSecurityException -
                Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[AffirmativeBased.class:3.2.4.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[AbstractSecurityInterceptor.class:3.2.4.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[FilterSecurityInterceptor.class:3.2.4.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84) ~[FilterSecurityInterceptor.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) [ExceptionTranslationFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) [ExceptionTranslationFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103) [SessionManagementFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113) [AnonymousAuthenticationFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154) [SecurityContextHolderAwareRequestFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45) [RequestCacheAwareFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199) [AbstractAuthenticationProcessingFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110) [LogoutFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57) [HeaderWriterFilter.class:3.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [OncePerRequestFilter.class:4.0.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87) [SecurityContextPersistenceFilter.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50) [WebAsyncManagerIntegrationFilter.class:3.2.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [OncePerRequestFilter.class:4.0.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [FilterChainProxy$VirtualFilterChain.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192) [FilterChainProxy.class:3.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160) [FilterChainProxy.class:3.2.4.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) [DelegatingFilterProxy.class:4.0.5.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) [DelegatingFilterProxy.class:4.0.5.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) [catalina.jar:8.0.12]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [catalina.jar:8.0.12]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219) [catalina.jar:8.0.12]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) [catalina.jar:8.0.12]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) [catalina.jar:8.0.12]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) [catalina.jar:8.0.12]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [catalina.jar:8.0.12]
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610) [catalina.jar:8.0.12]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) [catalina.jar:8.0.12]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:534) [catalina.jar:8.0.12]
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1081) [tomcat-coyote.jar:8.0.12]
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658) [tomcat-coyote.jar:8.0.12]
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222) [tomcat-coyote.jar:8.0.12]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566) [tomcat-coyote.jar:8.0.12]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523) [tomcat-coyote.jar:8.0.12]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_05]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_05]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-util.jar:8.0.12]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_05]

【问题讨论】:

【参考方案1】:

Spring 不会自动注入您的 AuthenticationEntryPoint,即使您使用 @Service 对其进行注释也是如此。您可以删除 @Service 注释并通过您的安全配置注入它,如下所示:

http.exceptionHandling()
    .authenticationEntryPoint(new MyAuthenticationEntryPoint())

【讨论】:

你能告诉为什么它不会注入 但是我的 RestExceptionTranslationFilter 仍然没有被调用。如何配置它 我已经添加了 http.addFilter(exceptionTranslationFilter());但我的自定义过滤器仍然没有被调用 您的过滤器被添加到过滤器链的末尾,身份验证将在过滤器链中较早发生,因此它永远没有机会处理异常。试试http.addFilterAfter(exceptionTranslationFilter(), ExceptionTranslationFilter.class)。这将在标准 ExceptionTranslationFilter 之后添加您的过滤器 它正在工作,我在 ExceptionTranslationFilter 之前添加了我的过滤器,一旦我添加它之后,它就开始工作了。我需要阅读并了解过滤器链在 Spring Security 中的工作原理。非常感谢

以上是关于spring 4 中自定义 ExceptionTranslationFilter 来处理 REST AuthenticationException的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot MVC 测试中自定义 bean

学习笔记Spring中自定义注解

Spring Boot中自定义注解

6.2 dubbo在spring中自定义xml标签源码解析

Spring MVC中自定义类型转换器(Date)

Spring MVC中自定义类型转换器(Date)