spring-bootspring-securityspring-security-ldap

How to configure ldap in spring-security 5.7 while retaining basic form login


I'm trying to configure my webSecurity to use both ldap and basic authentication (jdbc) with the new component-based security configuration (no WebSecurityConfigurerAdapter) but I can't get it to use both.

The required result is for spring to first attempt ldap, and if it doesn't find (or just fails for now is good enough) attempt to login using basic autentication.

The project is a migration from an older Spring-Boot version and with WebSecurityConfigurerAdapter the following code is what worked:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests().antMatchers("/services/**").permitAll().anyRequest().authenticated();
        http.httpBasic();
        http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
        http.logout().permitAll();

        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetails);

        //@formatter:off
        auth.ldapAuthentication()
            .userSearchFilter("(uid={0})")
            .userSearchBase("ou=people")
            .groupSearchFilter("(uniqueMember={0})")
            .groupSearchBase("ou=groups")
            .groupRoleAttribute("cn")
            .rolePrefix("ROLE_")
            .userDetailsContextMapper(customLdapUserDetailsContextMapper())
            .contextSource()
            .url(ldapUrl);
        //@formatter:on
    }

    @Bean
    CustomLdapUserDetailsContextMapper customLdapUserDetailsContextMapper()
    {
        CustomLdapUserDetailsContextMapper mapper = new CustomLdapUserDetailsContextMapper();
        mapper.setCustomUserDetailsService(userDetailsService());

        return mapper;
    }
    //Implementation of custom contextMapper is not relevant for example i believe, basicly it maps some ldap roles, but for testing i don't use roles yet
}

and this is what my conversion to the new style looks like:

@Configuration
public class WebSecurityConfig
{
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager ldapAuthenticationManager) throws Exception
    {

        // @formatter:off
        http.authorizeRequests()
            .mvcMatchers("/services/**").permitAll()
            .mvcMatchers("/resources/**").permitAll()
            .mvcMatchers("/webjars/**").permitAll()
            .anyRequest().authenticated();
        http.httpBasic();
        http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
        http.logout().permitAll();

        http.csrf().disable();

        http.authenticationManager(ldapAuthenticationManager); //THIS LINE SEEMS TO BE PROBLEMATIC
        // @formatter:on

        return http.build();
    }


    @Bean
    public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource ldapContextSource, UserDetailsService userDetailsService)
    {
        LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(ldapContextSource);

        UserDetailsServiceLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService);

        factory.setUserSearchFilter("(uid={0})");
        factory.setUserSearchBase("ou=people");
        factory.setLdapAuthoritiesPopulator(ldapAuthoritiesPopulator);

        return factory.createAuthenticationManager();
    }
}

when in the above new code the line http.authenticationManager(ldapAuthenticationManager); is enabled ldap login works fine (and it even binds roles from database user), but basic login doesn't work. however when the line is disabled basic login works but ldap does not.

Any help on how to get spring to use both logins would be much appreciated.


Solution

  • Instead of creating a custom AuthenticationManager, you can create the AuthenticationProvider that will be used for LDAP authentication.

    You can configure the provider on HttpSecurity:

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, LdapAuthenticator authenticator) throws Exception {
        // ...
        http.authenticationProvider(
                new LdapAuthenticationProvider(authenticator, ldapAuthoritiesPopulator));
        // ...
        return http.build();
    }
    
    @Bean
    BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
        BindAuthenticator authenticator = new BindAuthenticator(contextSource);
        authenticator.setUserSearch(
                new FilterBasedLdapUserSearch("ou=people", "(uid={0})", contextSource));
        return authenticator;
    }