Spring-ldap, how to love querying active directory

Because managing Active Directory entries is not so easy using java API, the Spring team provides the spring-ldap API, which is an amazing library !

What is this API about ?

Spring LDAP is a Java library for simplifying LDAP operations, based on the pattern of Spring’s JdbcTemplate. The framework relieves the user of common chores, such as looking up and closing contexts, looping through results, encoding/decoding values and filters, and more.

The connection

This API provides all you will need to query Active Directory simply and easily. Just because I wanted to use my AD object in a multi-threading environment, I tuned it using a pooled context source :

private static PoolingContextSource getContextSource(String url,
  String base, 
  String user, 
  String password) {
    LdapContextSource contextSource = new LdapContextSource();
    contextSource.setUrl(url);
    contextSource.setBase(base);
    contextSource.setUserDn(user);
    contextSource.setPassword(password);

    PoolingContextSource pooledContextSource = new PoolingContextSource();
    pooledContextSource.setContextSource(contextSource);
 
    return pooledContextSource;
}

By passing the AD url (e.g. ldap://hostname:389), the base realm to use (you can leave it empty), the user (e.g. can be cn=root) and the user password. If you set the base realm to a specific value (as, for example, “o=sample”), all dn results will miss this part.

Reading and writing context

When the context source is defined, you can use it and query it to retrieve a read only context with the associated method getReadOnlyContext(). Similar to the reading context, you can retrieve a write context by using the getReadWriteContext() method.

What about retrieving data ?

This part is pretty cool. Spring-ldap is based on bean usage, so you will be able to query your AD, and retrieving a specific bean using a mapper.

The LdapTemplate

The library allows you to use the LdapTemplate object. Just declare it using the following :

LdapTemplate ldapTemplate = new LdapTemplate(getPoolingContextSourceReplica());

 You said… A mapper ?

Yes, when querying the AD, you can provide a mapper, which will handle the query result, and building a business-related object (you can define a mapper for a user, for a group, for everything else…) :

public class UserMapper implements ContextMapper<LDAPUser> {
    @Override
    public User mapFromContext(Object ctx) throws NamingException {
        if (ctx == null) {
            return null;
        }
 
        DirContextAdapter context = (DirContextAdapter) ctx;
 
        User user = new User();
        user.setCn(context.getAttributes("cn"));
        user.setDn(context.getDn()); 

        return user;
    }
}

 Then the search

When it’s done, you can query it using a simple syntax. In this example, I want to get every AD user (objectclass is person) where the cn is “Doe” or “Doo” :

ldapTemplate.search(
  query().where("objectclass").is("person")
  .and(query().where("cn").is("Doe").or("cn").is("Doo")),
  new UserMapper());

By analyzing the search return, I have a User object, with a valorised dn and cn value !

Full code example

package fr.xoupix.ldap;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

import java.util.List;

import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.pool.factory.PoolingContextSource;

/**
 * Ldap search example
 */
public class LdapSearchExample {

    /**
     * Declaring a new connection
     * 
     * @param url The LDAP url
     * @param base The LDAP base realm
     * @param user The user login
     * @param password The user password
     * @return A valid {@link PoolingContextSource}
     */
    public static PoolingContextSource getContextSource(String url, String base, String user, String password) {
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(url);
        contextSource.setBase(base);
        contextSource.setUserDn(user);
        contextSource.setPassword(password);
        
        // Warning !
        // We need this call cause we are not using spring-bean
        contextSource.afterPropertiesSet();

        PoolingContextSource pooledContextSource = new PoolingContextSource();
        pooledContextSource.setContextSource(contextSource);
        
        return pooledContextSource;
    }

    /**
     * @param args Unused in the example
     * @throws Exception If there is an error during the search
     */
    public static void main(String[] args) throws Exception {
        PoolingContextSource poolingContextSource = LdapSearchExample.getContextSource(
                "ldap://192.168.56.101:389", "", "cn=root", "password");

        LdapTemplate ldapTemplate = new LdapTemplate(poolingContextSource);
        List<User> userList = ldapTemplate.search(
        	      query().where("objectclass").is("person")
        	             .and(query().where("cn").is("P8Admin")),
        	      new UserMapper());
        for (User user : userList) {
            System.out.println(user);
        }
    }

}
package fr.xoupix.ldap;

/**
 * The {@link User} bean
 */
public class User {

    // The user cn
    private String cn;
    
    // The user dn
    private String dn;

    /**
     * @return the cn
     */
    public String getCn() {
        return cn;
    }

    /**
     * @param cn the cn to set
     */
    public void setCn(String cn) {
        this.cn = cn;
    }

    /**
     * @return the dn
     */
    public String getDn() {
        return dn;
    }

    /**
     * @param dn the dn to set
     */
    public void setDn(String dn) {
        this.dn = dn;
    }

    @Override
    public String toString() {
        return "User [cn=" + this.cn + ", dn=" + this.dn + "]";
    }

}
package fr.xoupix.ldap;

import javax.naming.NamingException;

import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextAdapter;

/**
 * The {@link User} mapper
 */
public class UserMapper implements ContextMapper<User> {

    /**
     * Triggered after a search
     * 
     * @param ctx A search result object
     * @return A {@link User} object
     */
    public User mapFromContext(Object ctx) throws NamingException {
        
        if (ctx == null) {
            return null;
        }
        
        DirContextAdapter context = (DirContextAdapter) ctx;
        
        User user = new User();
        user.setCn(context.getStringAttribute("cn"));
        user.setDn(context.getDn().toString());
        
        return user;
    }

}

In console

User [cn=P8Admin, dn=cn=P8Admin,o=sample]
Please follow and like us:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.