Spring Security authentication with HTTP Basic and OIDC Bearer Token

Viewed 1902

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)

1 Answers

That is happening because the HttpBasicSecurityConfiguration is matching all the requests so only /api/func1/** and /api/func2/** are checked against http basic auth while others doesn't need to be authenticated. The Spring security filter chain is skipped at this point and the other filter is never triggered.

You need to restrict the requests to which the first filter is applied to. Just change the configure method in HttpBasicSecurityConfiguration to this:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http.csrf().disable()
            .requestMatchers().antMatchers("/api/func1/**","/api/func2/**")
            .and()
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .httpBasic()
            .authenticationEntryPoint(authenticationEntryPoint);
}

Notice that .requestMatchers().antMatchers("/api/func1/**","/api/func2/**") is applied before .authorizeRequests().anyRequest() and you can use any request then, there is no need to match url again.

Related