ExceptionHandler for a pre-controller Filter (Spring Security)

你。 提交于 2019-12-12 18:17:24

问题


I have a question about the exceptions on the Spring security level.

I user the SM_USER header for authorization and for the validation the request goes through the DelegateRequestMatchingFilter (it helps to understand whether the SM_USER is needed).

The problem is that if there is no SM_USER-header at all the line super.doFilter(...) throws a PreAuthenticatedCredentialsNotFoundException which cannot be processed with the standard ExceptionResolver for controllers and that's why looks strange and differs from all the other exception thrown by the application.

I tried to write an own method marked with @ExceptionResolver annotation for the filter, but it is ignored.

How can I insert an ExceptionsResolver for this Filter?

DelegateRequestMatchingFilter snippet

public class DelegateRequestMatchingFilter extends RequestHeaderAuthenticationFilter {

private RequestMatcher ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher matcher) {
    super();
    super.setPrincipalRequestHeader("SM_USER");
    this.ignoredRequests = matcher;
}   

@Override
public void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {

     HttpServletRequest httpReq = (HttpServletRequest) req;      
     if(ignoredRequests.matches(httpReq)) {
         chain.doFilter(req,resp);
     } else {
         super.doFilter(req,resp,chain);//<-- throws exception
     }       
}   
}

Fragment from ExceptionResolver which does not solve the problem because it works only for controllers

 @ControllerAdvice
public class ExceptionResolver extends AbstractHandlerExceptionResolver{


@Override
protected ModelAndView doResolveException(HttpServletRequest request,
        HttpServletResponse responce, Object handler, Exception exception) {

    ModelAndView toReturn = new ModelAndView();
    toReturn.setView(new MappingJackson2JsonView());
    toReturn.addObject("message", exception.getMessage());
    toReturn.addObject("exceptionClass", exception.getClass().getCanonicalName());
    return toReturn;
}

@ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)       
public ResponseEntity<Result> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {

    Result result = new Result("the identification header is missing");
    result.setException(PreAuthenticatedCredentialsNotFoundException.class);
    ResponseEntity<Result> response = new ResponseEntity<Result>(result, HttpStatus.FORBIDDEN);
    return response;
}
}

SecurityConfiguration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

private UserDetailsService userDetailsService;  
private PreAuthenticatedAuthenticationProvider preAuthenticatedProvider;



@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

    Map<String, List<GrantedAuthority>> rolesAuthorities = getRolesWithAutorities();

    userDetailsService = new CustomUserDetailsService(...);
    UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = 
            new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService);
    preAuthenticatedProvider = new PreAuthenticatedAuthenticationProvider();
    preAuthenticatedProvider.setPreAuthenticatedUserDetailsService(wrapper);
    auth.authenticationProvider(preAuthenticatedProvider);
}   

@Bean
public SmUserFailureHandler smUserFailureHandler(){
    return new SmUserFailureHandler();
}

@Bean
public AccessDeniedHandler accessDeniedHandler(){
    AccessDeniedHandlerImpl handler = new AccessDeniedHandlerImpl();
    handler.setErrorPage("/errorPage/");        
    return handler;     
}

public RequestHeaderAuthenticationFilter siteMinderFilter() throws Exception
{

    RequestHeaderAuthenticationFilter siteMinderFilter = new DelegateRequestMatchingFilter(
            new OrRequestMatcher(
            new AntPathRequestMatcher("/uriToSkip/")));

    siteMinderFilter.setPrincipalRequestHeader("SM_USER");
    siteMinderFilter.setAuthenticationManager(authenticationManager());   
    return siteMinderFilter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {  

 //...   
 http.addFilter(siteMinderFilter());
 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
  //adding methods... 
  http = registry.and();

  http.csrf().disable();
  http.headers().cacheControl().disable();
  http
  .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.NEVER);    
}
}

If I change my code according to the answer, I get this exception even SM_USEr header is present and is correct

org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:168)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)

回答1:


At first, you should add your filter after ExceptionTranslationFilter, this filter will handle the AccessDeniedException and AuthenticationException when it catches them.

http.addFilterAfter(siteMinderFilter(), ExceptionTranslationFilter.class);

and next you need inject a AuthenticationEntryPoint to handle AuthenticationException, there are some already exist AuthenticationEntryPoint you can use it, like HttpStatusEntryPoint, LoginUrlAuthenticationEntryPoint, ...;

If you want to return a JSON response you need to implement your own AuthenticationEntryPoint, for example:

public class ExampleAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        logger.error(authException.getMessage(), authException);
        if (!response.isCommitted()) {
            Map<String, Object> responseMsg = new HashMap<String, Object>();
            responseMsg.put("message", authException.getMessage());
            ObjectMapper mapper = new ObjectMapper();
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter writer = response.getWriter();
                writer.write(mapper.writeValueAsString(responseMsg));
                writer.flush();
                writer.close();
            } catch (IOException e) {
                // ignore
                logger.error(e.getMessage(), e);
            }
        }
    }
}

I think it's better to return http statue code 401 instead of 403, 403 is used for AccessDeniedException;

and then inject your own AuthenticationEntryPoint to ExceptionTranslationFilter;

http.exceptionHandling().authenticationEntryPoint(myEntryPoint());

@Bean
AuthenticationEntryPoint myEntryPoint() {
    return new ExampleAuthenticationEntryPoint();
}

/*
Or just return 401
AuthenticationEntryPoint myEntryPoint() {
    return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
} 
*/

BTW:

If you set continueFilterChainOnUnsuccessfulAuthentication to true in RequestHeaderAuthenticationFilter, the exception will not throw in super filter, and its default value is true, maybe you need to set it to false, or you will need to inject a AuthenticationFailureHandler in DelegateRequestMatchingFilter to handle the exception when do authentication.

** UPDATE **

else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);

                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                                "Full authentication is required to access this resource"));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);

                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }

As you can read from the code, now it's AccessDeniedException, I think you can disable the Anonymous user or you need to check why you get AccessDeniedException.

http.anonymous().disable();

UPDATE 2

Authentication currentUser = SecurityContextHolder.getContext()
            .getAuthentication();

    if (currentUser == null) {
        return true;
    }

    if (!checkForPrincipalChanges) {
        return false;
    }

    if(!principalChanged(request, currentUser)) {
        return false;
    }

default value of checkForPrincipalChanges is false, so now if you enable the anon filter, this will return false and skip do authentication in RequestHeaderAuthenticationFilter, so just try do disable anon filter. and you need to learn spring security code and research it by debugging.



来源:https://stackoverflow.com/questions/43632565/exceptionhandler-for-a-pre-controller-filter-spring-security

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!