I'm working on an web application that uses Spring Boot 2.4.1 and Spring Security 5.4.2, and I need to provide both HTTP Basic Authentication and Bearer Token Authentication (JWT access token is sent from a SPA for every API call). All API URLs start with path /api and must be authenticated using a Bearer Token except two URLs (/api/func1 and /api/func2) that are required to use HTTP Basic.
The problem is if I activate the class extending WebSecurityConfigurerAdapter for HTTP Basic, the bearer token authentication is skipped.
@Configuration
@EnableWebSecurity
@ConditionalOnProperty(name = "auth.enable", matchIfMissing = true)
@Order(1)
public class HttpBasicSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final RestBasicAuthEntryPoint authenticationEntryPoint;
private final DataSource dataSource;
@Autowired
public HttpBasicSecurityConfiguration(final RestBasicAuthEntryPoint authenticationEntryPoint,
final DataSource dataSource) {
super();
this.authenticationEntryPoint = authenticationEntryPoint;
this.dataSource = dataSource;
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers({"/api/func1/**","/api/func2/**"}).authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
// Usernames, passwords and roles are stored into USERS and AUTHORITIES tables
auth.jdbcAuthentication()
.passwordEncoder(passwordEncoder())
.dataSource(dataSource);
}
private PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
@Configuration
@EnableWebSecurity(debug=true)
@ConditionalOnProperty(name = "auth.enable", matchIfMissing = true)
@Order(2)
public class Oauth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
public Oauth2SecurityConfiguration(CactusSystemConfiguration systemConfiguration) {
super();
this.systemConfiguration = systemConfiguration;
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers({"/ws/**","/api/mock/**"}).permitAll()
.and()
.authorizeRequests().antMatchers("/api/**").authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
The Spring Security debug prints the following information when a method that needs to be authenticated with a bearer token is called :
Request received for GET '/api/genericFunc':
org.apache.catalina.connector.RequestFacade@517df0fb
servletPath:/api/genericFunc
pathInfo:null
headers:
host: devbox:8080
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
accept: */*
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
authorization: Bearer eyJ0eXAiOiJ.....
connection: keep-alive
cookie: JSESSIONID=D3E8CED6AB13EFF0D15BFA6C69AFEED4; JSESSIONID=C923091B9B9E38A19106BC4F529F7D13
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
Clearly BearerTokenAuthenticationFilter is not loaded.
If I exclude HttpBasicSecurityConfiguration from running then I get :
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
BearerTokenAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
Any idea why this happens? Maybe it is not possible to have two different authentication methods in Spring Security with APIs that have the same ancestor path (i.e. /api)