Search code examples
javaspringspring-securityspring-bootbasic-authentication

Using multiple WebSecurityConfigurerAdapter with different AuthenticationProviders (basic auth for API and LDAP for web app)


According the Spring Security Reference section 5.7 it should be possible to define more than one security adapter.

I try to do the same but without success. After a server reboot, the first x times the API works fine with basic auth, but after a couple of times I'm redirected to the login (form) page, this should only happen for our web app, not for the API calls.

My code:

@EnableWebSecurity
public class MultiHttpSecurityConfig  {

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private Environment env;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().
                withUser("admin").password("pw_test").roles(API_ROLE);
        }

        protected void configure(HttpSecurity http) throws Exception {
            http
              .antMatcher("/services/**")
              .authorizeRequests()
              .anyRequest().hasRole(API_ROLE)
              .and()
              .httpBasic()
              .and()
              .csrf()
              .disable();
        }
    }

    @Configuration
    @Order(2)
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private Environment env;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
            auth.eraseCredentials(false);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // LDAP FORM AUTHENTICATION
            http.authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .antMatchers("/css/**").permitAll() 
                .antMatchers("/js/**").permitAll() 
                .antMatchers("/images/**").permitAll() 
                .anyRequest().authenticated()
            .and().formLogin()
                .failureUrl("/login.html?error=1")
                .loginPage("/login.html")
                .loginProcessingUrl("/j_spring_security_check")
                .defaultSuccessUrl("/success.html")
                .usernameParameter("j_username")
                .passwordParameter("j_password")
                .permitAll();

            http.csrf().disable();

            // iFRAMES SETTINGS
            http
                .headers()
                .frameOptions().sameOrigin()
                .httpStrictTransportSecurity().disable();

            // HTTPS
            http
                .requiresChannel()
                .anyRequest()
                .requiresSecure();

            //MAP 8080 to HTTPS PORT
            http.portMapper().http(8080).mapsTo(443);
        }

        @Bean
        public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
            CustomLdapAuthenticationProvider provider = new CustomLdapAuthenticationProvider(env.getProperty("ldap.domain"), env.getProperty("ldap.url"), env.getProperty("ldap.base"));
            provider.setConvertSubErrorCodesToExceptions(true);
            provider.setUseAuthenticationRequestCredentials(true);
            return provider;
        }
    }
}

Any idea?

I'm using Spring Boot version 1.4.1-RELEASE and Spring Security version 4.1.3-RELEASE.


Solution

  • You use the same AuthenticationManager for both configurations, because you autowire the same AuthenticationManagerBuilder.

    See Spring Security Architecture:

    @Configuration
    public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
    
        ... // web stuff here
    
        @Autowired
        public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
            builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
                .password("secret").roles("USER");
        }
    
    }
    

    This example relates to a web application, but the usage of AuthenticationManagerBuilder is more widely applicable (see below for more detail on how web application security is implemented). Note that the AuthenticationManagerBuilder is @Autowired into a method in a @Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way:

    @Configuration
    public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
    
        @Autowired
        DataSource dataSource;
    
        ... // web stuff here
    
        @Override
        public void configure(AuthenticationManagerBuilder builder) {
            builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
                .password("secret").roles("USER");
        }
    
    }
    

    (using an @Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one.