Developing JNDI-based applications
Java applications, whether running as a stand-alone application, a servlet, or another form, can also utilize information stored in an LDAP-accessible directory. The industry-standard Java interface for connecting and interfacing with directories is called the Java Naming and Directory Interface (JNDI). JNDI is a Java Standard Extension. With JNDI, developers can connect seamlessly to multiple naming and directory services. They can build powerful and portable directory-enabled Java applications by using this interface. In this chapter, a sample application is provided that searches a directory, and displays the results to STDOUT. Another application is provided that updates certain attributes of a directory user. This sample application demonstrates the standard Java API calls used when working with LDAP directories. For more information on the JNDI interface and how to use it, refer to:
Any Java application, whether it is a servlet, a server application, or a client application, can be directory-enabled. Developers can exploit LDAP directory information, for example, for automatically addressing payment slips, retrieving user information at a user help desk, or performing application authentication. Developers can even serialize Java objects, such as GUI elements, into an LDAP directory and dynamically load them by all Java applications. The advantage of this method is, for example, that corporate-wide GUI design requirements can be deployed and changed very easily without recompiling programs or even touching the Java programs. The Java package that allows developers to directory-enable their applications is the Java Naming and Directory Interface (JNDI) developed by Sun Microsystems, Inc. There are also other Java LDAP clients available, for example the Java LDAP client from OpenLDAP (http://www.openldap.org). This client is written directly to the LDAP protocol. This chapter shows, based on a pair of sample applications, how to use the JNDI interface. However, it does not provide a complete description of the package and its included classes. For the most current information, as well as comprehensive tips for LDAP Users section, refer to the following Web page:
20.1 The JNDI
JNDI, defined by Sun Microsystems, Inc., provides naming and directory functionality to Java programs. JNDI is an API independent of any specific directory service implementation. It enables seamless access to directory objects through multiple naming facilities.
The definition prevents, by design, the appearance of any implementation-specific artifacts in the API. The unified API is designed to cover the common case. Providing this unified interface does not imply that access to unique features of a particular service, such as LDAP, is precluded; additional classes can be added to access service-unique features. JNDI can be used by a wide range of Java programs running on servers and traditional clients. JNDI can also accommodate a thin client by specifying a service provider that provides a proxy-style protocol where access to specific naming and directory services is relegated to a server. Security is dealt with by individual service providers; however, security-related problems can be returned to the client.
As discussed above, JNDI provides a generalized naming and directory service interface. For example, JNDI could be used to retrieve files from a file system. In this case, a file system acting as a naming service could return the file that is bound to a particular file name. JNDI could also be used to access an LDAP directory, performing searches and retrieving attributes. JNDI provides an API that applications use to access a naming and directory service. The naming and directory service could be provided by any of a variety of servers, such as LDAP, NDS, or a file system. JNDI provides a Service Provider Interface (SPI) that enables access to the particular underlying directory service.
JNDI provides classes that implement a naming interface for applications, such as the file system example, that only look up names and access objects bound to names. JNDI also provides a directory interface that extends the naming interface. The directory interface adds functionality to access attributes and schema.
In JNDI terminology, a name is made up of individual components called atomic names that correspond to RDNs in LDAP. A sequence of atomic names is a compound name. An LDAP DN is a compound name. Since the underlying naming and directory services can have different name syntaxes, the SPI provides an implementation of a NameParser that can break a name into its component parts. For example, LDAP RDNs are separated by commas; DNS names are separated by periods, and so on. Composite names are compound names that span different name spaces. For example, an LDAP URL can contain both a DNS and an LDAP name, as, for instance, in ldap://serverA.ibm.com/uid=mjordan,ou=people,o=ibm,c=us.
Names are interpreted within a context. A context can be thought of as a particular node in the Directory Information Tree (DIT). If the current context is o=ibm,c=us, then the atomic name ou=people refers to the child node in the DIT with the DN ou=people,o=ibm,c=us. The node ou=people,o=ibm,c=us is also called a subcontext of o=ibm,c=us. A name space is traversed from context to subcontext like a file system is traversed from a directory to the directory subtree.
The DirContext interface extends the Context interface by adding operations specific to a directory service such as accessing attributes and searching. An application must establish an initial directory context as a starting point from which to do searches or traverse the DIT. The initial directory context is usually the name of an LDAP server.
JNDI does provide a mechanism for using extended operations and extended responses, and provides some implementations of these, for example, the StartTLS operation. Searches use a search filter as defined in The String Representation of LDAP Search Filters, RFC 2254, which is available at http://www.ietf.org/rfc/rfc2254.txt?number=2254. A SearchControls object passed to the search method can be set to control search characteristics such as the scope of the search, the number of entries returned, the time limit, etc. Also, the entire schema name space can be browsed, and object and attribute schema definitions can be retrieved.
When a directory context is established, it is passed to an environment that contains preferences and controls to access the directory service. The environment specifies the SPI to use, the security level for binding to the server, and so on. The environment is a Hashtable or Properties list of (key, value) pairs. The environment settings could be coded in the application, retrieved from the system properties, or retrieved from a file. Table 20-1 lists some of the important environment properties.
Table 20-1 Environment settings and their descriptions
Environment property
Description
java.naming.factory.initial
Contains the class name of the initial context factory. The property value should be the fully qualified class name of the factory class that is being used to create an initial context.
java.naming.provider.url
LDAP URL that specifies the LDAP server.
java.naming.ldap.version
Specifies if server supports LDAP Version 2 or 3.
java.naming.referral
Specifies if referrals should be followed, ignored, or throw an exception.
java.naming.security.authentication
Authentication method used to bind to LDAP server: None, simple, or strong.
java.naming.security.principal
Distinguished name of user to authenticate.
java.naming.security.credentials
Password or other security credential.
java.naming.security.protocol
Specifies whether the connection to the LDAP server is secure (SSL).
20.2 Searching the directory
This section explains the JNDI methods required to search a directory using the JDNI interface. Performing searches is one of the most common functions JNDI is used for. Example 20-1 shows a sample Java application that performs a search on a directory and displays the results in LDIF format to STDOUT.
Example 20-1 Java application using JNDI that performs a directory search
import java.util.Hashtable;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.*;
import javax.naming.directory.*;
 
public class JavaSearch
{
public static void main(String args[])
{
InitialLdapContext ctx = null;
Hashtable hashtable = null;
 
// Set up default values for LDAP info
String url = "ldap://serverA.ibm.com:389";
String username = "cn=root";
String password = "password";
String base = "o=ibm,c=us";
 
try
{
// Set up LDAP config settings
hashtable = new Hashtable();
hashtable.put("java.naming.ldap.version", "3");
hashtable.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
hashtable.put("java.naming.security.authentication", "Simple");
hashtable.put("java.naming.referral", "follow");
 
hashtable.put("java.naming.provider.url", url);
hashtable.put("java.naming.security.principal", username);
hashtable.put("java.naming.security.credentials", password);
 
// Make LDAP connection
ctx = new InitialLdapContext(hashtable, null);
System.out.println("Connection established");
 
// Set up Search Controls
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
 
// perform search on directory
NamingEnumeration results = ctx.search(base, "(objectclass=inetorgperson)", sc);
 
// loop until we have gotten all entries returned by search
while (results.hasMore())
{
// get the SearchResult object
SearchResult sr = (SearchResult)results.next();
 
// ouptput DN of entry
System.out.println(sr.getName() + "," + base);
 
// get the attributes and attribute list
Attributes attrs = sr.getAttributes();
NamingEnumeration attrList = attrs.getAll();
 
// while we have attributes
while (attrList.hasMore())
{
Attribute attr = (Attribute)attrList.next();
 
// Get the attribute's values
NamingEnumeration values = attr.getAll();
while (values.hasMore())
{
// output the attribute name and value
System.out.println(attr.getID() + "=" + values.next());
}
}
System.out.println();
}
 
// Close the connection to LDAP
ctx.close();
}
catch (Exception ex)
{
System.out.println("EXCEPTION = " + ex.toString());
}
}
}
Each JNDI method used in the application will be described in detail. The sample application imports the JDNI packages shown in Example 20-2.
Example 20-2 JNDI packages that are imported
import java.util.Hashtable;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.*;
import javax.naming.directory.*;
20.2.1 Creating the directory context
A context can be thought of as a bind, in terms of API calls. The context specifies to which LDAP server to connect, what DN and password to use for the bind, what authentication method to use, and so forth. An instance of the javax.naming.ldap.InitialLdapContext class needs to be created. There are several constructors for this class. However, to properly initialize the context, an environment must be provided as the parameter. This can be accomplished by instantiating a Hashtable class called hashtable. The next step adds the following entries to the hashtable:
Context.INITIAL_CONTEXT_FACTORY - A constant that stores the name of the environment property for specifying the initial context factory to be used. The value of the property has to be the fully qualified class name of the factory class that will create an initial context. The application will use the default Sun LDAP factory, which is shipped with JNDI.
Context.PROVIDER_URL - This constant holds information about the LDAP server address and its port. In this case, the server address and port are provided via a variable url, which is defined at the beginning of the application.
Context.SECURITY_AUTHENTICATION - This constant specifies what kind of authentication is being used when binding to the directory server. The possible values depend on the service provider that is used. In this case, the application used the Sun JNDI interface, which supports the authentication mechanisms none, simple, and strong. Other service providers, such as IBM's JNDI might also support SASL or other values. The simple authentication method uses DNs and passwords in clear text for authentication.
Context.SECURITY_PRINCIPAL - This constant defines the DN to be used for authentication. The sample application uses the directory server's administrative DN of cn=root to authenticate.
Context.SECURITY_CREDENTIALS - This constant defines the password to be used for authentication.
The last step of creating the directory context is using the Hashtable hashtable as a parameter for the constructor InitialLdapContext constructor. The name of the context is later being used as a "handle" to the connection. The context can be used for search, get, and update operations.
20.2.2 Performing the search
After the context has been created, a search operation can be performed using the new context. The directory context provides several search() methods. The methods differ by the type and number of parameters they require, but they all have one thing in common, they return an enumeration of objects. The returned objects are instances of the SearchResult class.
The sample application uses the following search method:
public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException
The first parameter of the selected search() method specifies the name of the context or object to search. In the example, this parameter has the value o=ibm,c=us, or the top of the directory tree. This means the search will start at the top of the tree. If the directory tree had multiple subcontexts for each organization, the search could be narrowed to search for employees within a specific organization by specifying a subcontext for the search (for example - ou=employees,ou=Tivoli,o=ibm,c=us).
The second parameter represents the search filter. The filter specifies the search criteria. In the example, the search filter is (objectclass=inetorgperson). This filter will return all inetorgperson objects (standard person object) within the directory.
The third parameter defines the search controls. Search controls are used to control the behavior of a search operation. In this example, a search controls object sc is created. Two search control properties are set. The first one defines the scope of the search operation. A SUBTREE_SCOPE specifies that the search operations start at the search base defined in the first parameter of the search() method and searches through all subcontexts. The search could also be limited to just the context as defined in the search base. The second property defines the maximum number of search results that are returned. A value that is used in many applications is 100. This value should always be set to avoid a multitude of problems. Imagine a directory with 200,000 entries, and somebody searched for all entries that have an e-mail address. Assuming that all entries contain an e-mail address, the search would return all 200,000 entries. In this case, the client application might not be designed or have the required resources to handle all the responses. The SearchControls class documentation contains further information on all available properties that can be defined.
The search method returns, for each directory entry found, a separate instance of SearchResult in a NamingEnumeration object.
20.2.3 Processing the search results
At this point in the application, the search has found entries that match the search filter. The search results are stored in a NamingEnumeration object results. Each element in the NamingEnumeration object is an instance of the SearchResult class and contains all attributes returned by the search. The search results need to be processed in a nested approach, such as the while loop in the example.
The program checks first if the search returned a result by using the hasMore() method of the NamingEnumeration class. The while loop processes as long as there are more elements in the NamingEnumeration object answer. Within the while loop, the NamingEnumeration next() method is used to retrieve the next element and cast it to a SearchResult object sr. The sr object holds all attributes of the directory entry in the object class Attributes. The distinguished name of the object is then outputted to stdout.
Using the getAttributes() method of the SearchResult class, the attributes are retrieved from the search result and stored in the Attributes attrs object. Using the getAll() method of the Attributes object, a NamingEnumeration object attrList is returned with all of the Attribute objects available. By looping through the attrList object, each of the Attribute objects can be retrieved with the NamingEnumeration next() method. Finally, with the actual Attribute object attr, all of the attributes' values can be retrieved using the getAll() method. This returns another NamingEnumeration object called values that contains a list of the values. By looping through this values object, the attribute name and value pair can finally be outputted to stdout. If the attribute contains more than one value, than there will be a line for each value.
After all entries have been processed, the directory context ctx is closed and the example code is complete.
20.3 Changing a directory entry
This section explains the JNDI methods required to modify a directory entry using the JDNI interface. Performing modifications to entries is another common JNDI function. Adding, modifying, or deleting attributes or an entire entry also requires creating a context. However, there are different methods for creating or deleting entire directory entries or, in LDAP terms, subcontexts, and for adding, modifying, or deleting attributes for an individual entry. The context's createSubcontext() method is used to create a new entry and the destroySubcontext() method to remove or delete an entry. The modifyAttributes() method is used to add, modify, and delete attributes for a directory entry or subcontext. The sample application shows only the more complex task of modifying attributes. This section describes the important parts of the code for creating the context, getting all attributes of the entry to changed, and performing the update on the selected entry. Example 20-3 shows the sample Java application that performs a simple modification of an entry in the directory (replaces the current givenName attribute, adds an employeenumber attribute, and removes the telephoneNumber attribute).
Example 20-3 Java application using JNDI to change a directory entry
import java.util.Hashtable;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.*;
import javax.naming.directory.*;
 
public class JavaModify
{
public static void main(String args[])
{
InitialLdapContext ctx = null;
Hashtable hashtable = null;
 
// Set up default values for LDAP info
String url = "ldap://serverA.ibm.com:389";
String username = "cn=root";
String password = "password";
String base = "o=ibm,c=us";
 
try
{
// Set up LDAP config settings
hashtable = new Hashtable();
hashtable.put("java.naming.ldap.version", "3");
hashtable.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
hashtable.put("java.naming.security.authentication", "Simple");
hashtable.put("java.naming.referral", "follow");
 
hashtable.put("java.naming.provider.url", url);
hashtable.put("java.naming.security.principal", username);
hashtable.put("java.naming.security.credentials", password);
 
// Make LDAP connection
ctx = new InitialLdapContext(hashtable, null);
System.out.println("Connection established");
 
// Perform modifications
ModificationItem[] mods = new ModificationItem[3];
 
// replace (update) givenName attribute with 2 values
Attribute mod0 = new BasicAttribute("givenname", "Mike");
mod0.add("Michael");
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
 
// add employeeNumber attribute
Attribute mod1 = new BasicAttribute("employeenumber", "23");
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
 
// remove telephone number attribute
Attribute mod2 = new BasicAttribute("telephonenumber");
mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, mod2);
 
// Perform modification of user
ctx.modifyAttributes("uid=mjordan,ou=People," + base, mods);
System.out.println("Modification is complete");
 
// Close the connection to LDAP
ctx.close();
System.out.println("Connection ended");
}
catch (Exception ex)
{
System.out.println("EXCEPTION = " + ex.toString());
}
}
}
20.3.1 Creating the directory context
A context can be thought of as a bind, in terms of API calls. The context specifies to which LDAP server to connect, what DN and password to use for the bind, what authentication method to use, and so forth. An instance of the javax.naming.ldap.InitialLdapContext class needs to be created. There are several constructors for this class. However, to properly initialize the context, an environment must be provided as the parameter. This can be accomplished by instantiating a Hashtable class called hashtable. The next step adds the following entries to the hashtable:
Context.INITIAL_CONTEXT_FACTORY - A constant that stores the name of the environment property for specifying the initial context factory to be used. The value of the property has to be the fully qualified class name of the factory class that will create an initial context. The application will use the default Sun LDAP factory, which is shipped with JNDI.
Context.PROVIDER_URL - This constant holds information about the LDAP server address and its port. In this case, the server address and port are provided via a variable url, which is defined at the beginning of the application.
Context.SECURITY_AUTHENTICATION - This constant specifies what kind of authentication is being used when binding to the directory server. The possible values depend on the service provider that is used. In this case, the application used the Sun JNDI interface, which supports the authentication mechanisms none, simple, and strong. Other service providers, such as IBM's JNDI might also support SASL or other values. The simple authentication method uses DNs and passwords in clear text for authentication.
Context.SECURITY_PRINCIPAL - This constant defines the DN to be used for authentication. The sample application uses the directory server's administrative DN of cn=root to authenticate.
Context.SECURITY_CREDENTIALS - This constant defines the password to be used for authentication.
The last step of creating the directory context is using the Hashtable hashtable as a parameter for the constructor InitialLdapContext constructor. The name of the context is later being used as a "handle" to the connection. The context can be used for search, get, and update operations.
20.3.2 Performing the modification
The first thing needed to perform modifications, is an array of ModificationItem objects. One object will be needed for each attribute that needs to be added, removed, or replaced. In the sample application a ModificationItem array named mods is created with three elements. The next step is to fill this array with the proper ModificationItem objects. A ModificationObject constructor requires two things. First, it requires the modification to perform (ADD, REPLACE, and REMOVE). Second, it requires an Attribute object to use for the modification.
In the sample application, the first modification is to replace the user's first name (givenName attribute) with the values "Mike" and "Michael". A BasicAttribute object named mod0 is created with the attribute name of "givenName" and the initial value of "Mike". A second value "Michael" is then added to the mod0 object using the add() method of the BasicAttribute class. Finally, a ModificationItem is created in the first element of the mods array with a DirContext.REPLACE_ATTRIBUTE operation and the mod0 object.
The second modification is to add the employeeNumber attribute with the value "23". Again, a BasicAttribute object named mod1 is created with the attribute name of "employeeNumber" and the value of "23". Next, a ModificationItem is created in the second element of the mods array with a DirContext.ADD_ATTRIBUTE operation and the mod1 object.
The third modification is to remove the telephoneNumber attribute. A BasicAttribute object named mod2 is created with the attribute name of "telephoneNumber" with no values. Although a value could be specified, it does not make a difference because the attribute is being removed. The final ModificationItem is created in the third element of the mods array with a DirContext.REMOVE_ATTRIBUTE operation and the mod2 object.
Now that the ModifcationItem array is complete, the update to the directory entry can be performed. The InitialDirContext modifyAttributes() method is used to update the directory entry. The following modifyAttributes() method is used in the sample application:
public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException
This first parameter is a string representation of the directory entry's DN to be changed. In the sample application this value is "uid=mjordan,ou=People,o=ibm,c=us". The next parameter is the ModificationItem array mods that was created above. Assuming the call returns successfully, than the modifications are complete. Otherwise a NamingException would be thrown. After the call completes, the ctx is closed and the sample application is complete.
 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset