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 .
183914