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;
- X509AuthenticationFilter
- BasicAuthenticationFilter
- 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;
}
}
}