1

When multiple filters are added to the HttpSecurity configure method, they seem to be overlapping because only one works at the time.

This is the configure method:

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            .logout().and().antMatcher("/**")
            .addFilterBefore(ssoFilter(), RequestHeaderAuthenticationFilter.class)
            .authenticationProvider(preauthAuthProvider())
            .authorizeRequests()
            .antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
            .anyRequest().authenticated().and().csrf()
            .csrfTokenRepository(csrfTokenRepository()).and()
            .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}

I've tried to specify the order but the issue still persists:

@Bean
public FilterRegistrationBean securityFilterChain(@Qualifier(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) Filter securityFilter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
    registration.setOrder(Integer.MAX_VALUE - 2);
    registration.setName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
    return registration;
}

@Bean
public FilterRegistrationBean ssoFilterRegistrationBean() throws Exception {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(ssoFilter());
    registrationBean.setOrder(Integer.MAX_VALUE-1);
    return registrationBean;
}

@Bean
public FilterRegistrationBean csrfFilterRegistrationBean() throws Exception {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(csrfHeaderFilter());
    registrationBean.setOrder(Integer.MAX_VALUE);
    return registrationBean;
}

I've followed the following thread with no success.

Filter order in spring-boot

https://github.com/spring-projects/spring-boot/issues/1640

https://github.com/spring-projects/spring-boot/issues/677

Any help will be appreciated!

UPDATE:

CSRF Filter definition

private Filter csrfHeaderFilter() {
    return new OncePerRequestFilter() {
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            CsrfToken csrf = (CsrfToken) request
                    .getAttribute(CsrfToken.class.getName());
            if (csrf != null) {
                Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                String token = csrf.getToken();
                if (cookie == null
                        || token != null && !token.equals(cookie.getValue())) {
                    cookie = new Cookie("XSRF-TOKEN", token);
                    cookie.setPath("/");
                    response.addCookie(cookie);
                }
            }
            filterChain.doFilter(request, response);
        }
    };
}

SSO Filter definition:

public class SSORequestHeaderAuthenticationFilter extends RequestHeaderAuthenticationFilter {

private boolean allowPreAuthenticatedPrincipals = true;

public SSORequestHeaderAuthenticationFilter() {
    super();
    //TODO Pull this value from a properties file (application.properties, or localstrings.properties)
    //NOTE SM_USER is the default, but you can change it like this (your company may use some other header)
    //this.setPrincipalRequestHeader("SM_USER");
}


@Override
public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
    chain.doFilter(request, response);
}

/**
 * This is called when a request is made, the returned object identifies the
 * user and will either be {@literal null} or a String. This method will throw an exception if
 * exceptionIfHeaderMissing is set to true (default) and the required header is missing.
 *
 * @param request {@link javax.servlet.http.HttpServletRequest}
 */
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
    String userName = (String) (super.getPreAuthenticatedPrincipal(request));
    if (userName == null || userName.trim().equals("")) {
        return userName;
    }

    return userName;
}

public boolean isAllowPreAuthenticatedPrincipals() {
    return allowPreAuthenticatedPrincipals;
}
}
Community
  • 1
  • 1
Yanire Romero
  • 480
  • 3
  • 13
  • Can you add the filter code? – Cyril Jan 30 '16 at 10:31
  • Just a question. Are you using OAuth2 and @EnableOauth2Sso and try to authenticate requests containing a bearer token in the authorization header? I've done this a few days before. If this is what you want to achieve i could provide you a solution – Yannic Bürgmann Jan 30 '16 at 23:15
  • @YannicKlem oh god yes!!!! That's exactly my case, this application has the @ EnableOaut2Sso annotation and it keeps the token to send to another application as a redirect that's why I have the csrf filter :) thank that would really help! – Yanire Romero Jan 30 '16 at 23:40
  • Hope you can wait till wednessday.. i don't have access to the code right now and i want to try something before i provide this solution. – Yannic Bürgmann Jan 31 '16 at 09:31
  • I provided my solution. Please let me know if you found another solution, yet. – Yannic Bürgmann Feb 03 '16 at 17:31

3 Answers3

0

My guess is that you are not always executing FilterChain.doFilter method inside both filters. Then the filter chain stops and only one of your custom filters is executed. In this simple example both filters executed:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new Filter1(), RequestHeaderAuthenticationFilter.class)
                .addFilterAfter(new Filter2(), CsrfFilter.class)
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .logout()
                .permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }

    static class Filter1 extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            System.out.println("executed filter 1");
            filterChain.doFilter(request, response);
        }
    }

    static class Filter2 extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            System.out.println("executed filter 2");
            filterChain.doFilter(request, response);
        }
    }

}
Cyril
  • 2,376
  • 16
  • 21
0

You are confusing container registration (with FilterRegistrationBean) and registration in a Security filter chain (with HttpSecurity) and also possibly with the order of the filter chains within Spring Security. If a given filter chain is selected by Spring Security all the filters in it are not even necessarily fired anyway (filters can always switch off other downstream filters).

I suggest you stop worrying about the order in your FilterRegistrationBeans and use them to disable the container registration (by setting their enabled flag to false). Then think about the order of your filter chains, as specified by the @Order on your WebSecurityConfigurers. And finally you can decide if the order of the filters in a given chain matters, and if it does use the addFilter{Before,After} methods.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • Before giving it a try with the FilterRegistrationBean I was using the addFilterBefore and addFilterAfter but even when I did only the ssoFilter worked, the csrf was not even called. – Yanire Romero Jan 30 '16 at 23:42
  • Read the answer again. If your custom filters are declared as beans, then you need to disable the container registration. A filter not firing does not mean it is not registered. – Dave Syer Jan 31 '16 at 09:25
  • I did as you said and got the csrf filter working. Thing is, and I've looking around if someone experienced this before, the preAuthProvider *for which I use the ssoFilter() for), change the principal object completely hence loosing values that were relevant to the csrfFilter which has now became the issue. Is there a way to only change the authorities list in the Principal object during the application auth process? – Yanire Romero Jan 31 '16 at 15:46
  • This is now a different question? Probably better not to continue in comments. – Dave Syer Feb 01 '16 at 20:22
0

A Filter which does what you want to do does already exist. Its the OAuth2AuthenticationProcessingFilter. This filter is used if you annotate your application with @EnableResourceServer. If you do so this will cause, that only token based authentication will work now. You have to set the stateless flag of this filter to false to allow other ways of authentication, too.

What i did is to create a class ApiTokenAccessFilter which extends OAuth2AuthenticationProcessingFilter. This filter takes a ResourceServerTokenServices constructor parameter and sets the stateless flag to false.

public class ApiTokenAccessFilter extends OAuth2AuthenticationProcessingFilter {

  public ApiTokenAccessFilter(ResourceServerTokenServices resourceServerTokenServices) {

    super();
    setStateless(false);
    setAuthenticationManager(oauthAuthenticationManager(resourceServerTokenServices));
  }

  private AuthenticationManager oauthAuthenticationManager(ResourceServerTokenServices tokenServices) {

    OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();

    oauthAuthenticationManager.setResourceId("oauth2-resource");
    oauthAuthenticationManager.setTokenServices(tokenServices);
    oauthAuthenticationManager.setClientDetailsService(null);

    return oauthAuthenticationManager;
  }
}

In my security config i used this Filter as follows:

@Configuration
@EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private ResourceServerTokenServices tokenServices;

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

    http.authorizeRequests()
        .anyRequest()
        .authenticated()
        .and()
        .addFilterBefore(new ApiTokenAccessFilter(tokenServices), AbstractPreAuthenticatedProcessingFilter.class);
  }
}

I think this could be easier so i opened an issue on the spring-security-oauth Github repo. I'm not sure whether this solution is the way to go, but i didn't find another alternative.

Yannic Bürgmann
  • 6,301
  • 5
  • 43
  • 77