JNDI and OpenLDAP Tutorial

Preface

This tutorial is meant for users who wish to use Sun's JNDI in their applications connecting to OpenLDAP servers. I will discuss how to install and configure an OpenLDAP server and then go into details of connecting to and accessing the LDAP server using JNDI API.

I will not discuss how to use the JNDI's more advanced functions, but will give enough details to get you started in using OpenLDAP as the LDAP server used by JNDI. If you are not familiar with JNDI, Sun Microsystems has an excellent tutorial at: http://java.sun.com/products/jndi/tutorial/ .



Requirements

Software

OpenLDAP software and documentation obtained from http://www.openldap.org.

An operating system supported by OpenLDAP (most UNIX systems including Linux).

JDK from Sun Microsystems (version 1.3+ has built-in support for JNDI with the LDAP SPI).

I personally have tested and deployed these applications under Linux with Sun's JDK v1.4

Hardware

A machine to act as server (must have properly configured network connection if clients are not local).

Client machine must have properly configured network connection.

The Basics

The JNDI API is a powerful yet fairly simple API that can be used to access a wide variety of data sources. It is certainly not limited to LDAP or directories for that matter: using different SPIs (Service Provider Interfaces), JNDI can access other resources such as the file system or even DNS. We will focus here on using the LDAP SPI which comes bundled with JDK v1.3 and higher. Because LDAP is a standardized protocol (currently in version 3), the method for connecting to an OpenLDAP server applies equally well to connecting to any other LDAP server you might come across. The JNDI provides a high level interface for accessing a host of such services, taking care of all protocol-specific tasks (via the various SPIs).

JNDI can be used as a general purpose service for accessing and retrieving a mixed set of data. Using LDAP as a backend, JNDI allows us to store Java Objects directly into the directory and provides the user with APIs to make modifications to the tree. The power comes in storing objects inside the tree itself. For example, a company might develop a Java application that retrieves a “settings” object from an LDAP server upon startup. This object might be available under each user's entry in the directory on an LDAP server. In this way, the application can customize itself for each user, regardless of the machine on which the application is being used. This is, of course, just one possible use for the JNDI with an LDAP backend.

Getting Started

The first step is to compile OpenLDAP for your operating system. Depending on your installations, OpenLDAP might be available as a package in which case you will not need to recompile the program. Follow your operating system's instructions for installing this package. In all other cases, a compile is necessary and, luckily, is quite painless. Obtain an OpenLDAP tar archive from the site and save it on your local machine. Next, extract the archive:

tar -xvzf openldap-2.0.23.tgz [replace the file name with one you actually downloaded]

Next, move into the source directory and issue the following:

./configure

make depend

make

Hopefully, all will go well at this point and you will be ready to install the binaries into the system. On a linux machine, issue the following:

su -c 'make install'

Now, the openLDAP server binaries and configuration files should be installed on your system. On a Linux system, by default, the binaries are placed in: /usr/local/libexec/ and the configuration files are stored in: /usr/local/etc/openldap/ . Your installations may vary, especially if you have used a vendor's prepackaged version of the OpenLDAP software.

Configuration

The first thing to do now, is to configure the OpenLDAP server to support a directory structure and user requirements that you might have. Open the slapd.conf file from the configuration file directory in your favorite text editor. Please note that the file must be opened as a member of the group owning the configuration directory. On Linux systems, this is root. It should look like the following:






The main parts we will be concerning ourselves with are the schema, access control, and database definitions.

Schema

The LDAP server must know about the format in which you plan to store items into the directory. The schema is a description of this format and each type of object has a different schema. OpenLDAP comes bundled with a host of schemas under the schema/ directory in the configuration file directory. For our purposes, we will need to add in support for the Java Object schema. This definition will tell the LDAP server the exact format of the objects we will be storing inside the directory. Note, however, that the user need not be aware of any internals of this schema as JNDI takes care of the interactions with the LDAP server in storing objects into the directory. So, we simply add a single line before line #6 in the above illustration:

include /usr/local/etc/openldap/schema/java.schema

By default, the LDAP server makes sure that any requests for writing or making changes to the directory follow the schemas specified. This is a feature of the server to make applications robust and secure (you don't want people adding junk values to your directory) and can be turned on/off in OpenLDAP. We shall turn off schema checking for the purposes of this tutorial (read below to find out how).

Access Control

The commented region between lines 25 and 40 in the illustration specify access control privileges for each database. Using lines 31-35 as a model, you can modify the security privileges for any of the databases. In this example, however, we will leave these values unchanged for simplicity.

Database Definitions

Line 42 and beyond specify the various databases you wish to have available in the LDAP server. Line 46 specifies that we are making an ldbm database (the default). We must concern ourselves with the suffix and rootdn values for each database. The suffix is the origin of the database in a tree. This is usually something along the lines of: “dc=my-comp-name,dc=com” for a domain of my-comp-name.com. This should be familiar to any programmer using LDAP. In this example, we shall keep things simple and specify “o=myRoot” as the point of origin in the database. Next, we specify the rootdn, or administrator's distinguished name. This value will be used by clients for authenticating to the server. Simply follow the example in line 49, appending the suffix you made in the previous step to the “cn=Manager” portion. Finally, we specify the password to be used by this rootdn. Please note that by default, the password is stored in plain text in the configuation file. This is fine for testing purposes, but be sure to read the OpenLDAP manual for detailed information regarding the use of encrypted passwords.

The last few lines of the configuration file specify the directory in which the database will be stored along with any indices to maintain. We will not go over these issues in this tutorial, but more information can be found in the OpenLDAP documentation on the website. One thing to note, however, is that we will be turning off schema checking for this database to make our lives easier for testing. To do so, simply add a line with “schemacheck off” in the entry for the database.

An example configuration that we will use for testing purposes in this tutorial is:




Starting the Server

We are now ready to fire up the server and begin issuing queries. Please note that the server must be started by the super-user on the local system. Locate the directory in which the OpenLDAP server binaries were stored and issue the following command within that directory:

./slapd

If you are using an operating system whose vendor prepackaged the OpenLDAP software, there might already be a script prepared for you to invoke the server. Refer to your vendor's instructions in this case. The server should start in the background and you should be back to your shell prompt if all is ok.

By default, LDAP servers listen to connections on port 389 and OpenLDAP adheres to this convention by default. Now that the server is configured, we can begin working on the client.

Client Software

We will design and implement a simple JNDI application that connects to the LDAP server and stores/retrieves an java object from the directory. This application will, of course, be implemented using the JNDI APIs under Java.

First, we must be careful about understanding how the directory is structured. In our configuration file (slapd.conf), we specified that our “suffix” or root of the tree would be “o=jndiTest”. The server, however, has not created an object to represent this root node... it has merely specified that the root node should be an object called “o=jndiTest”. Thus, it is our repsonsibility to actually create the structure of the tree (adding the root node). This is a one-time requirement when the directory is afresh and creating the root node, therefore, will be our first order of business.

The following is a program called MakeRoot that will create the root context that we need for this example

/*
        MakeRoot.java -- basic JNDI application used for
        adding the "root" context to an LDAP server
*/


import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NameAlreadyBoundException;
import javax.naming.directory.*;
import java.util.*;

public class MakeRoot {
        
final static String ldapServerName = "localhost";
        
final static String rootdn = "cn=Manager, o=jndiTest";
        
final static String rootpass = "secret";
        
final static String rootContext = "o=jndiTest";
        
        
public static void main( String[] args ) {
                
// set up environment to access the server
                
                
Properties env = new Properties();
                
                
env.put( Context.INITIAL_CONTEXT_FACTORY,
                        
"com.sun.jndi.ldap.LdapCtxFactory" );
                
env.put( Context.PROVIDER_URL, "ldap://" + ldapServerName + "/" );
                
env.put( Context.SECURITY_PRINCIPAL, rootdn );
                
env.put( Context.SECURITY_CREDENTIALS, rootpass );
                
                
try {
                        
// obtain initial directory context using the environment
                        
DirContext ctx = new InitialDirContext( env );
                        
                        
// now, create the root context, which is just a subcontext
                        
// of this initial directory context.
                        
ctx.createSubcontext( rootContext );
                
} catch ( NameAlreadyBoundException nabe ) {
                        
System.err.println( rootContext + " has already been bound!" );
                
} catch ( Exception e ) {
                        
System.err.println( e );
                
}
        
}
}

// end MakeRoot.java

To compile this program, simply issue:

javac MakeRoot.java

and run it:

java MakeRoot

Any JNDI application undergoes similar steps in carrying out modifications to the directory. First, a Properties object is allocated to store the various parameters needed to obtain the Initial Context. The “Initial Context” is the first entry point into the directory for the application. In the above case, our entry point is just null as we must first create the root node object. We let JNDI know that we are trying to access an LDAP server by specifying the LDAP SPI, “com.sun.jndi.LdapCtxFactory” and specify the user name and password as we had stored in the slapd.conf file. Next, we are ready to obtain the initial context for the directory (DirContext) and can then carry out any operations we want on this initial context.

A directory browser is a very handy tool that should be used throughout the development of any JNDI application. Many commercial vendors supply viewers along with their directory servers. OpenLDAP, however, does not provide any such tool. As java developers, however, we are in luck: there is a Java based LDAP directory viewer available from http://www.iit.edu/~gawojar/ldap . This tool has a straightforward configuration process and can run on any platform supporting a decent JRE.

The following two snapshots show the effects of the above code when run on an OpenLDAP server connected to the localhost:

Before:




And after:




As you can see, the directory browser produced an error of “List Failed” before we ran the MakeRoot program. After running it, we see that the JNDI added the appropriate entries for the “o=jndiTest” object into the directory with attributes matching the schema for java objects.

Now that we have our root context in order, we can begin storing Java Objects into the directory. The framework code is the same as before, with the exception of the PROVIDER_URL. This value should now have “o=jndiTest” appended after the slash trailing the server host name. This tells JNDI what our initial context should be (now that we have a root node object, we can tell JNDI to use it as the initial context). The following example called TestLDAP stores an Integer Object into the directory and retrieves its value back from the directory.

/*
        TestLDAP.java -- basic JNDI application used for
        testing adding/retrieving objects from the server.
*/


import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NameAlreadyBoundException;
import javax.naming.directory.*;
import java.util.*;

public class TestLDAP {
        
final static String ldapServerName = "localhost";
        
final static String rootdn = "cn=Manager, o=jndiTest";
        
final static String rootpass = "secret";
        
final static String rootContext = "o=jndiTest";
        
        
public static void main( String[] args ) {
                
// set up environment to access the server
                
                
Properties env = new Properties();
                
                
env.put( Context.INITIAL_CONTEXT_FACTORY,
                        
"com.sun.jndi.ldap.LdapCtxFactory" );
                
env.put( Context.PROVIDER_URL, "ldap://" + ldapServerName + "/" + rootContext );
                
env.put( Context.SECURITY_PRINCIPAL, rootdn );
                
env.put( Context.SECURITY_CREDENTIALS, rootpass );
                
                
try {
                        
// obtain initial directory context using the environment
                        
DirContext ctx = new InitialDirContext( env );
                        
                        
// create some random number to add to the directory
                        
Integer i = new Integer( 28420 );
                        
                        
System.out.println( "Adding " + i + " to directory..." );
                        
ctx.bind( "cn=myRandomInt", i );
                        
                        
i = new Integer( 98765 );
                        
System.out.println( "i is now: " + i );
                        
                        
i = (Integer) ctx.lookup( "cn=myRandomInt" );
                        
System.out.println( "Retrieved i from directory with value: " + i );
                
} catch ( NameAlreadyBoundException nabe ) {
                        
System.err.println( "value has already been bound!" );
                
} catch ( Exception e ) {
                        
System.err.println( e );
                
}
        
}
}

// end TestLDAP.java

We follow the same instructions as before for compiling and running the program:

javac TestLDAP.java

java TestLDAP

You should see the following output when the program is run:

Adding 28420 to directory...

i is now: 98765

Retrieved i from directory with value: 28420

The code is self-explanatory and follows from the framework for JNDI applications outlined earlier. This time, however, we passed in the rootContext along with the PROVIDER_URL and binded the integer as “cn=myRandomInt”. The following two images show the effects this code has on the directory:

Before (same as the previous image):






After:




As you can see, JNDI stored the object into the directory following the appropriate schema for Java Objects. From the developer's point of view, this information is not really meaningful: we only need to concern ourselves with the bind/lookup methods on the DirContext to be able to add and lookup objects from the directory.

From here

You should now be confident in setting up, configuring, and using an OpenLDAP server for your requirements. The Sun Tutorial mentioned in the beginning is an excellent resource to exploring the advanced features of the JNDI API and the OpenLDAP website has more information about configuration settings and latest development news.

I hope this tutorial has been useful to you and your projects. If you have any comments/concerns/suggestions, feel free to mail me: linux_coder@poboxes.com .

191652