Simplify directory access with Spring LDAP

Try a Spring-based approach to LDAP programming with JNDI

Spring LDAP is a Spring-based framework that simplifies LDAP programming on the Java platform. In this step-by-step guide to using Spring LDAP you will learn how the framework handles the low-level coding required by most LDAP clients, so that you can focus on developing your application's business logic. You will also practice simple CRUD operations using Spring LDAP and learn about more advanced operations such as creating dynamic filters and converting LDAP entries into Java beans.

The Lightweight Directory Access Protocol is an essential component of most large-scale enterprise application deployments today. LDAP is primarily used to store information related to user identity, such as a user's username, password, and e-mail address. It is also used in security implementations where it is necessary to store user access rights for authentication and authorization purposes.

Java Naming and Directory Interface (JDNI) is the API used for LDAP programming on the Java platform. It defines a standard interface that can be used within your application to interact with any LDAP server. Unfortunately, using JNDI typically entails writing a lot of low-level, repetitive code. JNDI makes far too much work of simple procedures, such as ensuring that resources have been properly opened and closed. In addition, most JNDI methods throw checked exceptions, which are time-consuming to handle. Upon close inspection, it seems that 50 to 60 percent of the time spent programming JNDI is wasted on handling repetitive tasks.

Spring LDAP is an open source Java library designed to simplify LDAP programming on the Java platform. Just as the Spring Framework takes much of the low-level programming out of Java enterprise application development, Spring LDAP frees you from the infrastructural details of using LDAP. Rather than worrying about NamingExceptions and getting InitialContexts, you are free to concentrate on your application's business logic. Spring LDAP also defines a comprehensive unchecked exception hierarchy and provides helper classes for building LDAP filters and distinguished names.

Spring LDAP and JNDI

Note that the Spring LDAP framework does not replace JNDI. Rather, it provides wrapper and utility classes over JNDI to simplify LDAP programming on the Java platform.

In this article, a beginner's guide to using Spring LDAP, I will start by developing a simple JNDI program for executing an LDAP search. I'll then demonstrate how much easier it is to do the same thing using the Spring LDAP framework. I'll show you how to use Spring LDAP's AttributeMappers to map LDAP attributes to Java beans, and how to use its dynamic filters to build queries. Finally, I'll provide a step-by-step introduction to using the Spring LDAP framework to add, delete and modify data in your LDAP server.

Note that this article assumes you are familiar with the concepts and terminology of the Spring Framework. See the Resources section to learn more about the Spring Framework, LDAP and JNDI as well as to download the sample application.

A simple JNDI client

Listing 1 shows a simple JNDI program that will print out the cn attributes of all the Person type objects on your console.

Listing 1. SimpleLDAPClient.java

public class SimpleLDAPClient {
    public static void main(String[] args) {
        Hashtable env = new Hashtable();

        env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:10389/ou=system");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
        env.put(Context.SECURITY_CREDENTIALS, "secret");
        DirContext ctx = null;
        NamingEnumeration results = null;
        try {
            ctx = new InitialDirContext(env);
            SearchControls controls = new SearchControls();
            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            results = ctx.search("", "(objectclass=person)", controls);
            while (results.hasMore()) {
                SearchResult searchResult = (SearchResult) results.next();
                Attributes attributes = searchResult.getAttributes();
                Attribute attr = attributes.get("cn");
                String cn = (String) attr.get();
                System.out.println(" Person Common Name = " + cn);
            }
        } catch (NamingException e) {
            throw new RuntimeException(e);
        } finally {
            if (results != null) {
                try {
                    results.close();
                } catch (Exception e) {
                }
            }
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (Exception e) {
                }
            }
        }
    }
}

The first thing I've done in Listing 1 is to create an InitialDirContext object, which is then used as the context for the following directory operations. When creating a new Context object I configure properties such as the username, password and authentication mechanism that can be used to connect to the LDAP server. I've managed this by creating a Hashtable object, setting up all these properties as key/value pairs in the Hashtable and passing the Hashtable to the InitialDirContext constructor.

The immediate problem with this approach is that I've hard-coded all the configuration parameters into a .java file. This works fine for my example, but not for a real-world application. In a real-world application I would want to store the connection properties in a jndi.properties file and place that file in either my project's classpath or its <JAVA_HOME>/lib folder. Upon creation of a new InitialDirContext object, the JNDI API would look in both of those places for the jndi.properties file, then use it to create a connection to the LDAP server.

JNDI configuration parameters

Listing 2 shows the JNDI configuration parameters for connecting to my LDAP server. I explain the meaning of the parameters below.

Listing 2. JNDI configuration parameters for LDAP

java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url=ldap://localhost:10389/ou=system
java.naming.security.authentication=simple
java.naming.security.principal=uid=admin,ou=system
java.naming.security.credentials=secret
  1. Context.INITIAL_CONTEXT_FACTORY (java.naming.factory.initial) should be equal to the fully qualified class name that will be used to create a new initial context. If no value is specified then the NoInitialContextException is thrown.
  2. Context.PROVIDER_URL (java.naming.provider.url) should be equal to the URL of the LDAP server that you want to connect to. It should be in the format ldap://<hostname>:<port>.
  3. Context.SECURITY_AUTHENTICATION (java.naming.security.authentication) represents the type of authentication mechanism you want to use. I've used a username and password for authentication in my example, so the value of this property is simple.
  4. Context.SECURITY_PRINCIPAL (java.naming.security.principal) represents the distinguished username (DN) that should be used to establish a connection.
  5. Context.SECURITY_CREDENTIALS (java.naming.security.credentials) represents the user's password.

The JNDI client code

After getting the Context object my next step is to create a SearchControl object, which encapsulates the factors that determine the scope of my search and what will be returned. I want to search the entire subtree rooted at the context, so I set the search scope to SUBTREE_SCOPE by calling the setSearchScope() method of SearchControl, as previously shown in Listing 1.

Next, I call the search() method of DirContext, passing in (objectclass=person) as the value of the filter. The search() method will return a NamingEnumeration object containing all the entries in the subtree of Context, where objectclass is equal to person. After getting a NamingEnumeration as my result object, I iterate through it and print a cn attribute for each Person object.

That completes my explanation of the JNDI client code. Looking at SimpleLDAPClient.java, shown in Listing 1, you can easily see that more than half of the code goes toward opening and closing resources. Another problem with the JNDI API is that most of its methods will throw a NamingException or one of its subclasses in the case of an error. Because NamingException is a checked exception, you must handle it if it is thrown, but can you really recover from an exception if your LDAP server is down? No, you can't.

Most developers get around JNDI NamingExceptions by simply catching them and doing nothing. The trouble with this solution is that it can cause you to lose important information.

1 2 3 4 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more