0

In a Spring Boot 2.0.3 Application, I want to apply multiple authentication methods depending on request path.

Expected behavior

For requests matching;

  • /static/**
    • Do not apply any authentication filters, permit all
  • /secure/**
    • Expect and authenticate a client certificate
  • /api/**
    • Authenticate either by HTTP Basic or HTTP Digest

Observed behaviour

No matter what URL I request all authentication filters are always applied in the following order;

  1. X509AuthenticationFilter
  2. BasicAuthenticationFilter
  3. CustomDigestAuthenticationFilter

The filters work as expected and I have no problem authenticating with any of the methods. But in some edge cases applying the whole FilterChain causes problems, for example if the web browser has cached an Authorization header, then web browser will not ask for a client certificate.

So main question is; How do I isolate the different filters to their specific request domain?

Update/Edit

Followed input from Spring Security multiple url ruleset not working together and Spring Security Reference. The behavior unfortunately remains for this approach as well.

WebSecurityConfig Class

@EnableWebSecurity
public class WebSecurityConfig {

    private final CertificateAuthenticationProvider certificateAuthenticationProvider;
    private final CustomBasicAuthenticationProvider customBasicAuthenticationProvider;

    @Autowired
    public WebSecurityConfig(CertificateAuthenticationProvider certificateAuthenticationProvider, CustomBasicAuthenticationProvider customBasicAuthenticationProvider) {
        this.certificateAuthenticationProvider = certificateAuthenticationProvider;
        this.customBasicAuthenticationProvider = customBasicAuthenticationProvider;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(certificateAuthenticationProvider);
        auth.authenticationProvider(customBasicAuthenticationProvider);
    }

    @Configuration
    @Order(1)
    public static class SecureTestbedWebConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http    .antMatcher("/secure/**").authorizeRequests().anyRequest().authenticated()
                    .and().x509().x509AuthenticationFilter(x509AuthenticationFilter())
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
        }

        @Bean
        public CustomX509AuthenticationFilter x509AuthenticationFilter() {
            CustomX509AuthenticationFilter customX509AuthenticationFilter = new CustomX509AuthenticationFilter();
            customX509AuthenticationFilter.setAuthenticationManager(authenticationManager());
            return customX509AuthenticationFilter;
        }

        @Bean
        @Override
        protected AuthenticationManager authenticationManager() {
            try {
                return super.authenticationManager();
            } catch (Exception ex) {
                throw new IllegalStateException("Failed to extract AuthenticationManager.", ex);
            }
        }
    }

    @Configuration
    @Order(2)
    public static class ApiAuthenticationConfigurationAdapter extends WebSecurityConfigurerAdapter {

        private final CustomBasicAuthenticationEntryPoint customBasicAuthenticationEntryPoint;
        private final AccessDao accessDao;

        @Autowired
        public ApiAuthenticationConfigurationAdapter(CustomBasicAuthenticationEntryPoint customBasicAuthenticationEntryPoint, AccessDao accessDao) {
            this.customBasicAuthenticationEntryPoint = customBasicAuthenticationEntryPoint;
            this.accessDao = accessDao;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http    .antMatcher("/api/**")
                    .requestMatcher(new BasicRequestMatcher())
                    .authorizeRequests().anyRequest().authenticated()
                    .and().httpBasic().authenticationEntryPoint(customBasicAuthenticationEntryPoint)
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
                    .and().addFilterAfter(customDigestAuthenticationFilter(), BasicAuthenticationFilter.class);
        }

        @Bean
        public CustomDigestAuthenticationFilter customDigestAuthenticationFilter() {
            return new CustomDigestAuthenticationFilter(accessDao);
        }
    }

    @Configuration
    @Order(3)
    public static class NoAuthenticationNeededConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http    .antMatcher("/static/**").authorizeRequests()
                    .antMatchers(HttpMethod.GET).permitAll();
        }
    }

    private static class BasicRequestMatcher implements RequestMatcher {
        @Override
        public boolean matches(HttpServletRequest request) {
            String auth = request.getHeader("Authorization");
            boolean isMatching = auth != null && (auth.startsWith("Basic") || auth.startsWith("Digest"));
            log.debug("HTTP {} {}, isMatching; {}", request.getMethod(), request.getRequestURI(), isMatching);
            return isMatching;
        }
    }
}
aksamit
  • 2,325
  • 8
  • 28
  • 40
  • 1
    Thanks. Just double checked and as you stated antMatchers does not apply different rules as I was expecting. The other duplicate is exactly in line with what I am experiencing. Thanks for pointing this out, I hope I will be able to configure expected behavior now. – aksamit Oct 10 '18 at 20:17
  • Even with multiple WebSecurityConfiguration-classes I can observe that the same filters are applied no matter format of the URLs. Am I missing something? – aksamit Oct 10 '18 at 21:11
  • Got it to work. Removed the @Bean-annotation on the CustomDigestAuthenticationFilter. – aksamit Oct 10 '18 at 21:28
  • 1
    Have a look at https://stackoverflow.com/questions/39314176/filter-invoke-twice-when-register-as-spring-bean – dur Oct 11 '18 at 06:46

0 Answers0