How to support multiple Spring Security authentication providers in a single application and switch between them

Viewed 38

In my Java 11, Spring Boot 2.7.3, Maven application, I am using Spring Security in order to authenticate my users. To do this, I included the following dependency in my pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.3</version>
</dependency>

And then coded the following security configuration class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SecurityConfiguration {

    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new DocumentumAuthenticationProvider();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/content/login")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/content/logout")
                .logoutSuccessUrl("/content/logout")
                .permitAll();
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("**/content/css/**" ,"/content/js/**" ,"/content/images/**" ,"/error/**");
    }

    @Bean
    public BCryptPasswordEncoder encodePWD() {
        return new BCryptPasswordEncoder();
    }
}

Note that my custom authentication provider, DocumentumAuthenticationProvider, extends AbstractUserDetailsAuthenticationProvider. The user is prompted for credentials, which are then authenticated against a Documentum repository.

The above approach works fine.

Next, I wanted to implement SSO against Azure AD, so I removed the above security configuration class and custom authentication provider from the code set, and replaced the above pom.xml dependency with the following entries:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
        <version>2.7.3</version>
    </dependency>
    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
    </dependency>

I then added the following entries to my application.properties file, using values from a new application registration that I configured after logging into my Azure AD portal:

spring.cloud.azure.active-directory.enabled=true
spring.cloud.azure.active-directory.profile.tenant-id=xxxxxxxxxxxx
spring.cloud.azure.active-directory.credential.client-id=yyyyyyyyyyyy
spring.cloud.azure.active-directory.credential.client-secret=zzzzzzzzzz

Again, this all works fine.

The goal is now to have both these authentication approaches available together in my application and to make them switchable. Not surprisingly, if I simply merge the code snippets above all together, the application will not start because I have a WebSecurityConfigurerAdapter as well as SecurityFilterChain, which is not supportable:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
    ... 21 common frames omitted
Caused by: java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.

Many thanks for reading. I'd be very grateful for any suggestions / guidance as to how to proceed so that I can have both authentication providers sitting side by side in my code, with the ability to switch between them with a configuration setting.

0 Answers
Related