spring-securityactive-directoryspring-ldap

Simple Spring Boot LDAP authentication example does not work with ActiveDirectory


I found a very simple example for LDAP authentication, which works just fine using an embedded LDAP server: https://github.com/asbnotebook/spring-boot/tree/master/spring-security-embedded-ldap-example . It is exactly what I need - one config class added and now all users are required to log in before accessing the application.

Since our AD (local server, not the Azure AD) requires userDN and password for access, I added this to the example code, also modified url, base dn etc.

When I attempt to log in, I always get the "Bad credentials" error message. I then stepped through the code and found that the Spring LDAP code successfully retrieves some user data from AD (I found the user email address in the "userDetails" object which is known only in the AD), however the "password" field is set to null. This null value is then compared to the password entered by the user which fails and a BadCredentialsException is thrown in function org.springframework.security.authentication.dao.additionalAuthenticationChecks().

So now I have two questions:

  1. why is the "password" attribute set to null? My understanding is that it should contain the password hash. I checked the AD response with ldapsearch but I don't see anything looking like a password hash. However the userDN does work with other applications so it is probably not a problem with the userDN AD account. Please advise how to properly retrieve the password information.

  2. I believe that the example does not handle password hashes. The LDIF file to preload the embedded LDAP server of the example application simply contains clear text passwords for the userPassword attribute. Also the passwordEncoder in the example code looks like a No Op Encoder. How should I change this to make it work with the AD?

Here is my code:

package com.asbnotebook.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;

@Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        
        var cs = new DefaultSpringSecurityContextSource("ldaps://ad.company.local/dc=company,dc=local");
        cs.setUserDn("cn=robot1,ou=robots");
        cs.setPassword("secret");
        cs.afterPropertiesSet();

        var manager = new LdapUserDetailsManager(cs);
        manager.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=company_user", "cn"));
        manager.setGroupSearchBase("ou=company_groups");

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

Solution

  • After considering Gabriel Luci's comment, I have now found a simple way to authenticate with our ActiveDirectory:

    package com.asbnotebook.example.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
    
    @Configuration
    public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception
        {
            ActiveDirectoryLdapAuthenticationProvider adProvider =
                    new ActiveDirectoryLdapAuthenticationProvider(
                            "company.de","ldaps://ad.company.local","dc=company,dc=local");
            adProvider.setConvertSubErrorCodesToExceptions(true);
            adProvider.setUseAuthenticationRequestCredentials(true);
            auth.authenticationProvider(adProvider);
            auth.eraseCredentials(false);
        }
    }
    

    Login is possible using either the email address or sAMAccountName.