The new Certification Class of Learn Spring Security is out:

>> CHECK OUT THE COURSE

1. Overview

LDAP directory servers are read-optimized hierarchical data stores. Typically, they’re used for storing user-related information required for user authentication and authorization.

In this article, we’ll explore the Spring LDAP APIs to authenticate and search for users, as well as to create and modify users in the directory server. The same set of APIs can be used for managing any other type of entries in LDAP.

2. Maven Dependencies

Let’s begin by adding the required Maven dependency:

<dependency>
    <groupId>org.springframework.ldap</groupId>
    <artifactId>spring-ldap-core</artifactId>
    <version>2.3.1.RELEASE</version>
</dependency>

The latest version of this dependency can be found at spring-ldap-core.

3. Data Preparation

For the purpose of this article, let’s first create the following LDAP entry:

ou=users,dc=example,dc=com (objectClass=organizationalUnit)

Under this node, we will create new users, modify existing users, authenticate existing users and search for information.

4. Spring LDAP APIs

4.1. ContextSource & LdapTemplate Bean Definition

ContextSource is used for creating the LdapTemplate. We will see the use of ContextSource during user authentication in the next section:

@Bean
public LdapContextSource contextSource() {
    LdapContextSource contextSource = new LdapContextSource();
    
    contextSource.setUrl(env.getRequiredProperty("ldap.url"));
    contextSource.setBase(
      env.getRequiredProperty("ldap.partitionSuffix"));
    contextSource.setUserDn(
      env.getRequiredProperty("ldap.principal"));
    contextSource.setPassword(
      env.getRequiredProperty("ldap.password"));
    
    return contextSource;
}

LdapTemplate is used for creation and modification of LDAP entries:

@Bean
public LdapTemplate ldapTemplate() {
    return new LdapTemplate(contextSource());
}

4.2. User Authentication

Let’s now implement a simple piece of logic to authenticate an existing user:

public void authenticate(String username, String password) {
    contextSource
      .getContext(
        "cn=" + 
         username + 
         ",ou=users," + 
         env.getRequiredProperty("ldap.partitionSuffix"), password);
}

4.3. User Creation

Next, let’s create a new user and store an SHA hash of the password in LDAP.

At the time of authentication, the LDAP server generates the SHA hash of the supplied password and compares it to the stored one:

public void create(String username, String password) {
    Name dn = LdapNameBuilder
      .newInstance()
      .add("ou", "users")
      .add("cn", username)
      .build();
    DirContextAdapter context = new DirContextAdapter(dn);

    context.setAttributeValues(
      "objectclass", 
      new String[] 
        { "top", 
          "person", 
          "organizationalPerson", 
          "inetOrgPerson" });
    context.setAttributeValue("cn", username);
    context.setAttributeValue("sn", username);
    context.setAttributeValue
      ("userPassword", digestSHA(password));

    ldapTemplate.bind(context);
}

digestSHA() is a custom method which returns the Base64 encoded string of the SHA hash of the supplied password.

Finally, the bind() method of LdapTemplate is used to create an entry in the LDAP server.

4.4. User Modification

We can modify an existing user or entry with the following method:

public void modify(String username, String password) {
    Name dn = LdapNameBuilder.newInstance()
      .add("ou", "users")
      .add("cn", username)
      .build();
    DirContextOperations context 
      = ldapTemplate.lookupContext(dn);

    context.setAttributeValues
      ("objectclass", 
          new String[] 
            { "top", 
              "person", 
              "organizationalPerson", 
              "inetOrgPerson" });
    context.setAttributeValue("cn", username);
    context.setAttributeValue("sn", username);
    context.setAttributeValue("userPassword", 
      digestSHA(password));

    ldapTemplate.modifyAttributes(context);
}

The lookupContext() method is used to find the supplied user.

4.5. User Search

We can search for existing users using search filters:

public List<String> search(String username) {
    return ldapTemplate
      .search(
        "ou=users", 
        "cn=" + username, 
        (AttributesMapper<String>) attrs -> (String) attrs.get("cn").get());
}

The AttributesMapper is used to get the desired attribute value from the entries found. Internally, Spring LdapTemplate invokes the AttributesMapper for all the entries found and creates a list of the attribute values.

5. Testing

spring-ldap-test provides an embedded LDAP server based on ApacheDS 1.5.5. To setup the embedded LDAP server for testing, we need to configure the following Spring bean:

@Bean
public TestContextSourceFactoryBean testContextSource() {
    TestContextSourceFactoryBean contextSource 
      = new TestContextSourceFactoryBean();
    
    contextSource.setDefaultPartitionName(
      env.getRequiredProperty("ldap.partition"));
    contextSource.setDefaultPartitionSuffix(
      env.getRequiredProperty("ldap.partitionSuffix"));
    contextSource.setPrincipal(
      env.getRequiredProperty("ldap.principal"));
    contextSource.setPassword(
      env.getRequiredProperty("ldap.password"));
    contextSource.setLdifFile(
      resourceLoader.getResource(
        env.getRequiredProperty("ldap.ldiffile")));
    contextSource.setPort(
      Integer.valueOf(
        env.getRequiredProperty("ldap.port")));
    return contextSource;
}

Let’s test our user search method with JUnit:

@Test
public void 
  givenLdapClient_whenCorrectSearchFilter_thenEntriesReturned() {
    List<String> users = ldapClient
      .search(SEARCH_STRING);
 
    assertThat(users, Matchers.containsInAnyOrder(USER2, USER3));
}

6. Conclusion

In this article, we have introduced Spring LDAP APIs and developed simple methods for user authentication, user search, user creation and modification in an LDAP server.

As always the full source code is available in this Github project. The tests are created under Maven profile “live” and hence can be run using the option “-P live”.

Go deeper into Spring Security with the course:

>> LEARN SPRING SECURITY