Search code examples
spring-bootspring-security

Authenticate through oauth server and api key at the same time


In Spring Boot, is it possible to authenticate clients using both OAuth2 and API Keys? Let's say I have an application with UI. The user logs in the UI and given a jwt token from an OAuth2 server. So the security config looks like this

...
http.authorizeRequests()
    ....
    .anyRequest().authenticated().and()
    .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

The oauth2 server is configured in application.yml file.

Say, I want to expose some of the backend APIs to other clients and only authorized them by authenticating the API Key. Can I just add a addFilterBefore in my security config? Does it mean it will authenticate both api key and oauth jwt?

http.addFilterBefore(new SomeApiKeyFilter(...))
    ....
    .anyRequest().authenticated().and()
    .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

Is this possible? Cause when I tried to call an endpoint, it throws an error saying ... APIKeyAuthenticationToken cannot be cast to JwtAuthenticationToken.


Solution

  • As @ch4mp told, you have to define two security configurations. I did It by adding two innerclasses (as a configuration) in my security configuration class instead of creating two Beans. Let me show you an example:

    public class SecurityConfig {
    
      @Configuration
      @Order(1)
      public static class DefaultSecurityConfig {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
            http.securityMatcher("/xxx/**")
                .authorizeHttpRequests(auth -> auth
                     .anyRequest().authenticated()
                )
                ...
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    
            return http.build();
        }
    
      }
    
      @Configuration
      @Order(2)
      public static class OtherSecurityConfig {
    
          @Bean
        public SecurityFilterChain otherFilterChain(HttpSecurity http) throws Exception {
    
            http.securityMatcher("/yyy/**")
                .authorizeHttpRequests(auth -> auth
                            .anyRequest().authenticated()
                )
                .formLogin()
                ...;
    
            return http.build();
        }
      }
    
    }
    

    You will get a form login if you try to access the yyy base path and 401 http error if you try the xxx base path.