Naming Exceptions
Many methods in the JNDI packages throw NamingException when they need to indicate that the requested operation could not be performed. Typically, you will see try/catch wrappers around methods that might throw a NamingException:
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup("somename");
} catch (NamingException e) {
// Handle the error
System.err.println(e);
}
Exception Class Hierarchy
JNDI has a rich exception hierarchy derived from the NamingException class. The class names are self-explanatory and listed here.
To handle a specific subclass of NamingException, you need to catch the subclass individually. For example, the following code handles AuthenticationException and its subclasses specially.
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup("somename");
} catch (AuthenticationException e) {
// attempt to reacquire the authentication information
...
} catch (NamingException e) {
// Handle the error
System.err.println(e);
}
Enumerations
Operations such as Context.list() and DirContext.search() return a NamingEnumeration. In these cases, if an error occurs and no results are returned, a NamingException (or an appropriate subclass) is thrown at the time the method is called. If an error occurs but there are some results to return, a NamingEnumeration is returned so that you can get those results. When all results are exhausted, calling NamingEnumeration.hasMore() will cause a NamingException (or one of its subclasses) to be thrown to indicate the error. At that point, the enumeration becomes invalid and no more methods should be called on it.
For example, if you perform a search() and specify a count limit of n answers to return, then search() will return an enumeration of at most n results. If the number of results exceeds n, then a SizeLimitExceededException is thrown when the n+1st call to NamingEnumeration.hasMore() is made. Refer to the sample code on result counting in this lesson.
Examples in This Tutorial
In the inline sample code embedded in this tutorial text, try/catch clauses are often omitted for readability. Often, because only code snippets are shown here, only the lines directly used to illustrate the concept are included. If you look at the source files that accompany this tutorial, you will see the appropriate placement of try/catch clauses for NamingException.
The exceptions in the javax.naming package can be found here.
Looking Up an Object
To look up an object from a naming service, use Context.lookup() and pass the name of the object you want to retrieve. Suppose that an object named cn=Rosanna Lee,ou=People exists in the naming service. To retrieve the object, you would write
Object obj = ctx.lookup("cn=Rosanna Lee,ou=People");
The type of the object returned by lookup() depends on the underlying naming system and the data associated with the object itself. A naming system can contain many different types of objects, and looking up an object in different parts of the system may yield different types of objects. In this example, "cn=Rosanna Lee,ou=People" happens to be bound to a context object (javax.naming.ldap.LdapContext). You can cast the result of lookup() to the target class.
For example, the following code looks up the object "cn=Rosanna Lee,ou=People" and casts it to LdapContext.
import javax.naming.ldap.LdapContext;
...
LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");
The complete example is in the file Lookup.java.
There are two new static methods in Java SE 6 that can be used for looking up a name:
InitialContext.doLookup(Name name)InitialContext.doLookup(String name)
These methods provide a shortcut for looking up a name without having to instantiate an InitialContext.
Listing the Context
Instead of getting one object at a time using Context.lookup(), you can list an entire context in a single operation. There are two methods for listing a context: one returns bindings, and the other returns only name-to-class-name pairs.
Context.list() Method
Context.list() returns an enumeration of NameClassPair. Each NameClassPair contains the object's name and class name. The following code snippet lists the contents of the "ou=People" directory (i.e., the files and directories found in the "ou=People" directory).
NamingEnumeration list = ctx.list("ou=People");
while (list.hasMore()) {
NameClassPair nc = (NameClassPair)list.next();
System.out.println(nc);
}
Running this example produces the following output.
# java List
cn=Jon Ruiz: javax.naming.directory.DirContext
cn=Scott Seligman: javax.naming.directory.DirContext
cn=Samuel Clemens: javax.naming.directory.DirContext
cn=Rosanna Lee: javax.naming.directory.DirContext
cn=Maxine Erlund: javax.naming.directory.DirContext
cn=Niels Bohr: javax.naming.directory.DirContext
cn=Uri Geller: javax.naming.directory.DirContext
cn=Colleen Sullivan: javax.naming.directory.DirContext
cn=Vinnie Ryan: javax.naming.directory.DirContext
cn=Rod Serling: javax.naming.directory.DirContext
cn=Jonathan Wood: javax.naming.directory.DirContext
cn=Aravindan Ranganathan: javax.naming.directory.DirContext
cn=Ian Anderson: javax.naming.directory.DirContext
cn=Lao Tzu: javax.naming.directory.DirContext
cn=Don Knuth: javax.naming.directory.DirContext
cn=Roger Waters: javax.naming.directory.DirContext
cn=Ben Dubin: javax.naming.directory.DirContext
cn=Spuds Mackenzie: javax.naming.directory.DirContext
cn=John Fowler: javax.naming.directory.DirContext
cn=Londo Mollari: javax.naming.directory.DirContext
cn=Ted Geisel: javax.naming.directory.DirContext
Context.listBindings() Method
Context.listBindings() returns an enumeration of Binding. Binding is a subclass of NameClassPair. A binding not only contains the object's name and class name, but also the object itself. The following code enumerates the "ou=People" context, printing the name and object for each binding.
NamingEnumeration bindings = ctx.listBindings("ou=People");
while (bindings.hasMore()) {
Binding bd = (Binding)bindings.next();
System.out.println(bd.getName() + ": " + bd.getObject());
}
Running this example produces the following output.
# java ListBindings
cn=Jon Ruiz: com.sun.jndi.ldap.LdapCtx@1d4c61c
cn=Scott Seligman: com.sun.jndi.ldap.LdapCtx@1a626f
cn=Samuel Clemens: com.sun.jndi.ldap.LdapCtx@34a1fc
cn=Rosanna Lee: com.sun.jndi.ldap.LdapCtx@176c74b
cn=Maxine Erlund: com.sun.jndi.ldap.LdapCtx@11b9fb1
cn=Niels Bohr: com.sun.jndi.ldap.LdapCtx@913fe2
cn=Uri Geller: com.sun.jndi.ldap.LdapCtx@12558d6
cn=Colleen Sullivan: com.sun.jndi.ldap.LdapCtx@eb7859
cn=Vinnie Ryan: com.sun.jndi.ldap.LdapCtx@12a54f9
cn=Rod Serling: com.sun.jndi.ldap.LdapCtx@30e280
cn=Jonathan Wood: com.sun.jndi.ldap.LdapCtx@16672d6
cn=Aravindan Ranganathan: com.sun.jndi.ldap.LdapCtx@fd54d6
cn=Ian Anderson: com.sun.jndi.ldap.LdapCtx@1415de6
cn=Lao Tzu: com.sun.jndi.ldap.LdapCtx@7bd9f2
cn=Don Knuth: com.sun.jndi.ldap.LdapCtx@121cc40
cn=Roger Waters: com.sun.jndi.ldap.LdapCtx@443226
cn=Ben Dubin: com.sun.jndi.ldap.LdapCtx@1386000
cn=Spuds Mackenzie: com.sun.jndi.ldap.LdapCtx@26d4f1
cn=John Fowler: com.sun.jndi.ldap.LdapCtx@1662dc8
cn=Londo Mollari: com.sun.jndi.ldap.LdapCtx@147c5fc
cn=Ted Geisel: com.sun.jndi.ldap.LdapCtx@3eca90
Terminating a NamingEnumeration
A NamingEnumeration can terminate naturally, explicitly, or unexpectedly.
- When
NamingEnumeration.hasMore()returnsfalse, the enumeration is complete and effectively terminated. - You can explicitly terminate the enumeration before it is complete by calling
NamingEnumeration.close(). This provides a hint to the underlying implementation to release any resources associated with the enumeration. - If
hasMore()ornext()throws aNamingException, the enumeration is effectively terminated.
No matter how the enumeration is terminated, once terminated, it cannot be used anymore. Calling methods on a terminated enumeration yields undefined results.
Why Two Different Listing Methods?
list() is suitable for browser-style applications that just need to display the names of objects in a context. For example, a browser might list the names in a context and wait for the user to select one or a few names for further action. Such applications typically do not need access to all objects in the context.
listBindings() is suitable for applications that need to perform bulk operations on the objects in a context. For example, a backup application might need to execute a "file stats" operation on all objects in a file directory. Or a printer management program might want to restart all printers in a building. To perform these operations, these applications need to get all the objects bound in the context. Therefore, it is more convenient to have the object return as part of the enumeration.
An application can use list() or the potentially more expensive listBindings(), depending on the type of information it needs.
Adding, Replacing, or Removing a Binding
The Context interface contains methods for adding, replacing, and removing bindings in a context.
Adding a Binding
Context.bind() is used to add a binding to a context. It takes the name of the object and the object to be bound as arguments.
Before proceeding: The examples in this lesson require you to make additions to the schema. You must either turn off schema checking in your LDAP server or add the schema accompanying this tutorial to the server. Both of these tasks are typically performed by the directory server administrator. See the LDAP Setup lesson.
// Create the object to be bound
Fruit fruit = new Fruit("orange");
// Perform the bind
ctx.bind("cn=Favorite Fruit", fruit);
This example creates an object of the Fruit class and binds it to the name "cn=Favorite Fruit" in the context ctx. If you subsequently look up the name "cn=Favorite Fruit" in ctx, you will get the fruit object. Note that to compile the Fruit class, you need the FruitFactory class.
If you run this example twice, the second attempt will fail with a NameAlreadyBoundException. This is because the name "cn=Favorite Fruit" is already bound. For the second atempt to succeed, you must use rebind().
Adding or Replacing a Binding
rebind() is used to add or replace a binding. It takes the same arguments as bind(), but the semantics are that if the name is already bound, it will be unbound and the new given object will be bound.
// Create the object to be bound
Fruit fruit = new Fruit("lemon");
// Perform the bind
ctx.rebind("cn=Favorite Fruit", fruit);
When you run this example, it will replace the binding created by the bind() example.
Removing a Binding
To remove a binding, you can use unbind().
// Remove the binding
ctx.unbind("cn=Favorite Fruit");
When run, this example will remove the binding created by the bind() or rebind() examples.
Renaming
You can rename an object in a context using Context.rename().
// Rename to Scott S
ctx.rename("cn=Scott Seligman", "cn=Scott S");
This example renames the object bound to "cn=Scott Seligman" to "cn=Scott S". After verifying that the object has been renamed, the program renames it back to its original name ("cn=Scott Seligman").
// Rename back to Scott Seligman
ctx.rename("cn=Scott S", "cn=Scott Seligman");
For more examples on renaming LDAP entries, check out the Advanced Topics for LDAP Users lesson.
Creating and Destroying Subcontexts
The Context interface contains methods for creating and destroying subcontexts, that is, contexts that are bound in another context of the same type.
The examples described here use objects with attributes and create a subcontext in a directory. You can use these DirContext methods to associate attributes with an object when adding a binding or subcontext to the namespace. For example, you could create a Person object and bind it to the namespace, associating attributes about the Person object at the same time. The naming equivalent would have no attributes.
createSubcontext() differs from bind() in that it creates a new object, i.e., a new context, binding it to the directory, whereas bind() binds the given object to the directory.
Creating a Context
To create a named context, you supply the name of the context to be created to createSubcontext(). To create a context with attributes, you supply the name of the context to be created and its attributes to DirContext.createSubcontext().
Before proceeding: The examples in this lesson require you to make additions to the schema. You must either turn off schema checking in your LDAP server or add the schema accompanying this tutorial to the server. Both of these tasks are typically performed by the directory server administrator. See the LDAP Setup lesson.
// Create attributes to be associated with the new context
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);
// Create the context
Context result = ctx.createSubcontext("NewOu", attrs);
This example creates a new context named "ou=NewOu" in the context ctx with an attribute "objectclass" having the values "top" and "organizationalUnit".
# java Create
ou=Groups: javax.naming.directory.DirContext
ou=People: javax.naming.directory.DirContext
ou=NewOu: javax.naming.directory.DirContext
This example creates a new context named "NewOu" that is a subcontext of ctx.
Destroying a Context
To destroy a context, you supply the name of the context to be destroyed to destroySubcontext().
// Destroy the context
ctx.destroySubcontext("NewOu");
This example destroys the context "NewOu" in the context ctx.
Attribute Names
An attribute consists of an attribute identifier and a set of attribute values. The attribute identifier, also called the attribute name, is a string that identifies the attribute. The attribute values are the contents of the attribute, and their type is not restricted to strings. You use the attribute name when you want to specify a particular attribute for retrieval, searching, or modification. Names are also returned by operations that return attributes, such as when performing a read or search in the directory.
When using attribute names, you need to be aware of certain directory server features so that you are not surprised by the results. These features are described in the next subsections.
Attribute Types
In directories such as LDAP, the name of an attribute identifies the type of the attribute, often called an attribute type name. For example, the attribute name "cn" is also called an attribute type name. The definition of the type of an attribute specifies the syntax that attribute values should have, whether it can have multiple values, and the equality and ordering rules to use when performing comparison and sorting operations.
Attribute Subclassing
Some directory implementations support attribute subclassing, where the server allows an attribute type to be defined in terms of another attribute type. For example, the "name" attribute might be a superclass of all name-related attributes: "commonName" might be a subclass of "name". For directory implementations that support this feature, requesting the "name" attribute might return the "commonName" attribute.
When accessing directories that support attribute subclassing, you must be aware that the server might return attributes with names different from the one you requested. To minimize the chance of this happening, use the most derived subclass.
Attribute Name Synonyms
Some directory implementations support synonyms for attribute names. For example, "cn" might be a synonym for "commonName". Thus, a request for the "cn" attribute might return the "commonName" attribute.
When accessing directories that support attribute name synonyms, you must be aware that the server might return attributes with names different from the one you requested. To prevent this from happening, use the canonical attribute name rather than its synonyms. The canonical attribute name is the name used in the attribute definition; synonyms are names that point to the canonical attribute name in their definitions.
Language Preferences
An extension to LDAP v3 (RFC 2596) allows you to specify language codes alongside attribute names. This is similar to attribute subclassing, where one attribute name can represent multiple distinct attributes. For example, a "description" attribute with two language variants:
description: software
description;lang-en: software products
description;lang-de: Softwareprodukte
Requesting the "description" attribute will return all three attributes.
When accessing directories that support this feature, you must be aware that the server might return attributes with names different from the one you requested.
Reading Attributes
To read an object's attributes from the directory, use DirContext.getAttributes() and pass the name of the object for which you want the attributes. Suppose that an object in the naming service has the name "cn=Ted Geisel, ou=People". To retrieve this object's attributes, you need code similar to the following:
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");
You can print the contents of this answer as follows.
for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
Attribute attr = (Attribute)ae.next();
System.out.println("attribute: " + attr.getID());
/* Print each value */
for (NamingEnumeration e = attr.getAll(); e.hasMore();
System.out.println("value: " + e.next()))
;
}
This will produce the following output.
# java GetattrsAll
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: telephonenumber
value: +1 408 555 5252
attribute: cn
value: Ted Geisel
Returning Selected Attributes
To read a selective subset of attributes, you provide an array of strings that are the attribute identifiers of the attributes you want to retrieve.
// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
// Get the attributes requested
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);
This example requests the "sn", "telephonenumber", "golfhandicap", and "mail" attributes of the object "cn=Ted Geisel, ou=People". The object has all of these attributes except "golfhandicap", so three attributes are returned in the answer. Here is the output of the example.
# java Getattrs
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252
Modifying Attributes
The DirContext interface contains methods for modifying the attributes and attribute values of objects in the directory.
Using a Modification List
One way to modify an object's attributes is to provide a list of modification requests (ModificationItem). Each ModificationItem consists of a numeric constant indicating the type of modification to make, and an Attribute describing the modification to be made. The three types of modifications are:
- Add an attribute
- Replace an attribute
- Remove an attribute
Modifications are applied in the order they appear in the list. Either all modifications are performed or none are performed.
The following code creates a list of modifications. It replaces the value of the "mail" attribute with the value "geisel@wizards.com", adds an additional value to the "telephonenumber" attribute, and removes the "jpegphoto" attribute.
// Specify the changes to make
ModificationItem[] mods = new ModificationItem[3];
// Replace the "mail" attribute with a new value
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("mail", "geisel@wizards.com"));
// Add an additional value to "telephonenumber"
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
new BasicAttribute("telephonenumber", "+1 555 555 5555"));
// Remove the "jpegphoto" attribute
mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
new BasicAttribute("jpegphoto"));
Windows Active Directory: Active Directory defines "telephonenumber" as a single-valued attribute, contrary to RFC 2256. For this example to work against Active Directory, you must use an attribute other than "telephonenumber", or change DirContext.ADD_ATTRIBUTE to DirContext.REPLACE_ATTRIBUTE.
After this modification list is created, you can supply it to modifyAttributes() as follows.
// Perform the requested modifications on the named object
ctx.modifyAttributes(name, mods);
Using Attributes
Alternatively, you can perform modifications by specifying the type of modification and the attributes to apply the modification to.
For example, the following line replaces the attributes of orig (identified in name) with those in orig:
ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, orig);
Any other attributes of name are left untouched.
Both usages of modifyAttributes() are demonstrated in the sample program. The program modifies attributes using a modification list, and then restores the original attributes using the second form of modifyAttributes().
Adding, Replacing, and Binding with Attributes
Discussed how the naming examples use bind() and rebind(). The DirContext interface contains overloaded versions of these methods that accept attributes. You can use these DirContext methods to associate attributes with an object when adding a binding or subcontext to the namespace. For example, you could create a Person object and bind it to the namespace, associating attributes about the Person object at the same time.
Adding a Binding with Attributes
DirContext.bind() is used to add a binding with attributes to a context. It takes the name of the object, the object to be bound, and a set of attributes as arguments.
// Create the object to be bound
Fruit fruit = new Fruit("orange");
// Create attributes to be associated with the object
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);
// Perform bind
ctx.bind("ou=favorite, ou=Fruits", fruit, attrs);
This example creates an object of the Fruit class and binds it to the name "ou=favorite" relative to the context named "ou=Fruits" in ctx. This binding has the attribute "objectclass". If you subsequently look up the name "ou=favorite, ou=Fruits" in ctx, you will get the fruit object. Then, getting the attributes of "ou=favorite, ou=Fruits" gives you the attributes that were used when the object was created. Here is the output of this example.
# java Bind
orange
attribute: objectclass
value: top
value: organizationalUnit
value: javaObject
value: javaNamingReference
attribute: javaclassname
value: Fruit
attribute: javafactory
value: FruitFactory
attribute: javareferenceaddress
value: #0#fruit#orange
attribute: ou
value: favorite
The extra attributes and attribute values shown are used to store information about the object (fruit). These extra attributes are discussed in more detail in the tutorial.
If you run this example twice, the second attempt will fail with a NameAlreadyBoundException. This is because the name "ou=favorite" is already bound in the "ou=Fruits" context. For the second attempt to succeed, you need to use rebind().
Replacing a Binding with Attributes
DirContext.rebind() is used to add or replace a binding and its attributes. It takes the same arguments as bind(). However, the semantics of rebind() are that if the name is already bound, it will be unbound, and the new given object and attributes will be bound.
// Create the object to be bound
Fruit fruit = new Fruit("lemon");
// Create attributes to be associated with the object
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);
// Perform bind
ctx.rebind("ou=favorite, ou=Fruits", fruit, attrs);
When you run this example, it will replace the binding created by the bind() example.
# java Rebind
lemon
attribute: objectclass
value: top
value: organizationalUnit
value: javaObject
value: javaNamingReference
attribute: javaclassname
value: Fruit
attribute: javafactory
value: FruitFactory
attribute: javareferenceaddress
value: #0#fruit#lemon
attribute: ou
value: favorite
Search
One of the most useful features that directories provide is their yellow pages or search service. You can compose a query consisting of attributes of the entry you are looking for and submit that query to the directory. The directory then returns a list of entries that satisfy the query. For example, you could ask the directory to return all entries whose batting average is greater than 200, or all entries that represent persons whose last names begin with "Sch".
The DirContext interface provides several methods for searching a directory, with increasing complexity and functionality. The aspects of searching a directory are covered in the following sections:
- Basic Search
- Search Filters
- Search Controls
Basic Search
The simplest form of search requires you to specify the set of attributes that an entry must have, and the name of the target context in which to perform the search.
The following code creates an attribute set matchAttrs with two attributes "sn" and "mail". It specifies that qualifying entries must have a surname ("sn") attribute with the value "Geisel" and a "mail" attribute of any value. It then calls DirContext.search() to search the context "ou=People" for entries that have the attributes specified in matchAttrs.
// Specify the attributes to match
// Ask for objects that have a surname ("sn") attribute with
// the value "Geisel" and the "mail" attribute
// ignore attribute name case
Attributes matchAttrs = new BasicAttributes(true);
matchAttrs.put(new BasicAttribute("sn", "Geisel"));
matchAttrs.put(new BasicAttribute("mail"));
// Search for objects that have those matching attributes
NamingEnumeration answer = ctx.search("ou=People", matchAttrs);
You can print the results as follows.
while (answer.hasMore()) {
SearchResult sr = (SearchResult)answer.next();
System.out.println(">>>" + sr.getName());
printAttrs(sr.getAttributes());
}
printAttrs() is similar to the code for printing attribute sets in the getAttributes() example.
Running this example produces the following result.
# java SearchRetAll
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
Returning Selected Attributes
The previous example returns all the attributes associated with the entries satisfying the specified query criterion. You can select the attributes to be returned by passing to search() an array of attribute identifiers to include in the result. After creating matchAttrs as shown earlier, you also need to create the array of attribute identifiers like this.
// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
// Search for objects that have those matching attributes
NamingEnumeration answer = ctx.search("ou=People", matchAttrs, attrIDs);
This example returns entries that have the attributes "sn", "telephonenumber", "golfhandicap", and "mail", which have a "mail" attribute and have an "sn" attribute with the value "Geisel". This example produces the following result. (The entry does not have a "golfhandicap" attribute, so it is not returned.)
# java Search
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252
Filters
In addition to specifying a search using a set of attributes, you can specify a search in the form of a search filter. A search filter is a search query expressed in the form of a logical expression. The syntax of search filters accepted by DirContext.search() is described in RFC 2254.
The following search filter specifies that qualifying entries must have an "sn" attribute with the value "Geisel" and a "mail" attribute of any value:
(&(sn=Geisel)(mail=*))
The following code creates a filter and default SearchControls and uses them to perform the search. This search is equivalent to the one presented in the Basic Search section.
// Create the default search controls
SearchControls ctls = new SearchControls();
// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";
// Search for objects using the filter
NamingEnumeration answer = ctx.search("ou=People", filter, ctls);
Running this example produces the following result.
# java SearchWithFilterRetAll
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd75e
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
Search Filter Syntax Quick Overview
The search filter syntax is essentially a logical expression in prefix notation (i.e., the logical operator appears before its arguments). The following table lists the symbols used to create filters.
| Symbol | Description |
|---|---|
| & | Conjunction (i.e., and — all items in the list must be true) |
| | | Disjunction (i.e., or — one or more alternatives must be true) |
| ! | Negation (i.e., not — the negated item must be false) |
| = | Equals (according to attribute matching rules) |
| ~= | Approximately equals (according to attribute matching rules) |
| >= | Greater than (according to attribute matching rules) |
| <= | Less than (according to attribute matching rules) |
| =* | Presence (i.e., the entry must have the attribute, but its value is irrelevant) |
| * | Wildcard (indicates zero or more characters can appear at that position); used when specifying attribute values to match |
| \ | Escape (used to escape '*', '(', or ')' when they appear in attribute values) |
Each item in the filter is composed of an attribute identifier and an attribute value or a symbol representing an attribute value. For example, the item "sn=Geisel" means that the "sn" attribute must have the attribute value "Geisel", while the item "mail=*" means that the "mail" attribute must be present.
Each item must be enclosed in parentheses, such as "(sn=Geisel)". These items are combined using logical operators such as "&" (conjunction) to create logical expressions like "(& (sn=Geisel) (mail=*))".
Each logical expression can be further composed of items that are themselves logical expressions, as in "(| (& (sn=Geisel) (mail=*)) (sn=L*))". This last example requests entries that have the "sn" attribute equal to "Geisel" and the "mail" attribute, or whose "sn" attribute starts with the letter "L".
For a complete description of the syntax, see RFC 2254.
Returning Selected Attributes
The previous example returns all the attributes associated with the entries satisfying the specified filter. You can select the attributes to be returned by setting the search controls parameter. You can create an array of attribute identifiers to include in the result and pass it to SearchControls.setReturningAttributes(). Here is an example.
// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);
This example is equivalent to the Returning Selected Attributes example in the Basic Search section. Running this example produces the following result. (The entry does not have the "golfhandicap" attribute, so it is not returned.)
# java SearchWithFilter
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252
Scope
The default SearchControls specifies that the search is performed in the named context (SearchControls.ONELEVEL_SCOPE). This default setting is used in the examples in the Search Filters section.
In addition to this default, you can specify that the search be performed in the entire subtree or only in the named object.
Searching the Subtree
Searching the entire subtree searches the named object and all its descendants. To cause the search to be performed in this way, pass SearchControls.SUBTREE_SCOPE to SearchControls.setSearchScope() as follows.
// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";
// Search the subtree for objects by using the filter
NamingEnumeration answer = ctx.search("", filter, ctls);
This example searches the subtree of context ctx for entries that satisfy the specified filter. It finds the entry "cn=Ted Geisel, ou=People" in this subtree that satisfies the filter.
# java SearchSubtree
>>>cn=Ted Geisel, ou=People
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252
Searching the Named Object
You can also search the named object itself. For example, it is useful to test whether a named object meets a search filter. To search the named object, pass SearchControls.OBJECT_SCOPE to setSearchScope().
// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";
// Search the subtree for objects by using the filter
NamingEnumeration answer =
ctx.search("cn=Ted Geisel, ou=People", filter, ctls);
This example tests whether the object "cn=Ted Geisel, ou=People" satisfies the given filter.
# java SearchObject
>>>
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252
The example finds one answer and prints it. Notice that the name of the result is the empty string. This is because the object's name is always relative to the search context (in this case, "cn=Ted Geisel, ou=People").
Result Counting
Sometimes a query may produce too many answers, and you want to limit the number of answers returned. You can do this by using the count limit search control. By default, searches have no count limit—they will return all the answers they find. To set a count limit on a search, pass a number to SearchControls.setCountLimit().
The following example sets the count limit to 1.
// Set the search controls to limit the count to 1
SearchControls ctls = new SearchControls();
ctls.setCountLimit(1);
If the program tries to get more results than the count limit, a SizeLimitExceededException is thrown. Therefore, if a program sets a count limit, it should either differentiate this exception from other NamingExceptions, or keep track of the count limit and not request more than that number of results.
Specifying a count limit on a search is one way to control the resources consumed by an application, such as memory and network bandwidth. Other ways to control resource consumption include narrowing your search filter (being more specific about what you are looking for), starting the search at an appropriate context, and using an appropriate scope.
Time Limit
Setting a time limit on a search places an upper bound on the time a search operation waits for a reply. This is useful when you do not want to wait too long for a response. If the specified time limit is exceeded before the search operation completes, a TimeLimitExceededException is thrown.
To set a time limit on a search, pass the number of milliseconds to SearchControls.setTimeLimit(). The following example sets the time limit to 1 second.
// Set the search controls to limit the time to 1 second (1000 ms)
SearchControls ctls = new SearchControls();
ctls.setTimeLimit(1000);
To get this particular example to exceed its time limit, you would need to reconfigure it to use either a slow server or one with a large number of entries. Or, you could use other strategies to make the search take longer than 1 second.
A time limit of zero means that no time limit is set, and the call to the directory will wait indefinitely for a reply.
Troubleshooting Tips
The most common problems you might encounter when running successfully compiled programs that use JNDI classes are as follows.
-
No initial context
-
Connection refused
-
Connection failure
-
Program hangs
-
Name not found
-
Unable to connect to arbitrary hosts
-
Unable to access system properties for configuration
-
Unable to authenticate using CRAM-MD5
-
You get a
NoInitialContextException.
Cause: You have not specified an implementation for the initial context. Specifically, the Context.INITIAL_CONTEXT_FACTORY environment property is not set to the class name of the factory that will create the initial context. Or, you have not made the program available to the service provider class (named by Context.INITIAL_CONTEXT_FACTORY).
Solution: Set the Context.INITIAL_CONTEXT_FACTORY environment property to the class name of the initial context implementation you are using. See the Configuration section for details.
If the property is already set, make sure the class name is not misspelled and that the named class is available to your program (in its classpath or installed in the JRE's jre/lib/ext directory). The Java platform includes service providers for LDAP, COS naming, DNS, and the RMI registry. All other service providers must be installed and added to the execution environment.
- You get a
CommunicationExceptionindicating "Connection refused".
Cause: The server and port identified by the Context.PROVIDER_URL environment property are not being served by a server. Perhaps someone has disabled or shut down the machine running the server. Or, you may have misspelled the server's name or port number.
Solution: Check that there is indeed a server running on that port, and restart the server if necessary. How you perform this check depends on the LDAP server you are using. Typically, an administrative console or tool is available to manage the server. You can use that tool to verify the server's status.
- The LDAP server responds to other utilities, such as its administrative console, but does not appear to respond to requests from your program.
Cause: The server does not respond to LDAP v3 connection requests. Some servers (especially public servers) do not respond properly to LDAP v3, instead ignoring the request rather than rejecting it. Additionally, some LDAP v3 servers have issues with the controls automatically sent by Oracle's LDAP service provider and often return server-specific failure codes.
Solution. Try setting the environment property "java.naming.ldap.version" to "2". The LDAP service provider defaults to trying LDAP v3 to connect to the LDAP server; if that fails, it uses LDAP v2. If the server silently ignores the v3 request, the provider will assume the request succeeded. To work around such server problems, you must explicitly set the protocol version to ensure correct server behavior.
If the server is a v3 server, try setting the following environment property before creating the initial context:
env.put(Context.REFERRAL, "throw");
This will turn off the controls automatically sent by the LDAP provider. (See the JNDI Tutorial for details.)
- Program hangs.
Cause: Some servers (especially public servers) do not respond (or even give a negative reply) when trying to perform a search that would generate too many results, or that requires the server to examine too many entries to generate an answer. These servers are trying to limit the amount of resources they consume on a per-request basis.
Or, you are trying to communicate using Secure Sockets Layer (SSL) with a server/port that does not support it, or vice versa (i.e., you are trying to communicate with an SSL port using a plain socket).
Finally, the server may be responding very slowly due to heavy load, or not responding at all for some reason.
Solution: If your program hangs because the server is trying to limit its use of resources, retry your request with a query that will return a single result or only a few results. This will help you determine whether the server is alive. If it is, you can broaden your initial query and resubmit.
If your program hangs due to SSL issues, you need to find out whether the port is an SSL port, and then set the Context.SECURITY_PROTOCOL environment property appropriately. If the port is an SSL port, you should set this property to "ssl". If it is not an SSL port, you should not set this property.
If your program hangs for one of the above reasons, the property com.sun.jndi.ldap.read.timeout comes in handy for specifying a read timeout. The value of this property is the string representation of an integer representing the read timeout in milliseconds for LDAP operations. If the LDAP provider cannot get an LDAP response within that time period, it aborts the read attempt. The integer should be greater than zero. An integer less than or equal to zero means no read timeout is specified, which is equivalent to waiting for a response indefinitely.
If this property is not specified, the default is to wait for a response until it is received.
For example,
env.put("com.sun.jndi.ldap.read.timeout", "5000"); causes the LDAP service provider to abort the read attempt if the server does not reply within 5 seconds.
- You get a
NameNotFoundException.
Cause: When you initialize the initial context for LDAP, you supplied a root distinguished name. For example, if you set the Context.PROVIDER_URL environment property to "ldap://ldapserver:389/o=JNDITutorial" for the initial context, and then supplied a name like "cn=Joe,c=us", the full name you pass to the LDAP service will be "cn=Joe,c=us,o=JNDITutorial". If this is indeed what you intended, check your server to make sure it contains such an entry.
Additionally, Oracle Directory Server also returns this error if you supply an incorrect distinguished name for authentication purposes. For example, if you set the Context.SECURITY_PRINCIPAL environment property to "cn=Admin, o=Tutorial", and "cn=Admin, o=Tutorial" is not an entry on the LDAP server, then the LDAP provider will throw a NameNotFoundException. In reality, Oracle Directory Server should return an authentication-related error instead of "name not found".
Solution: Verify that the name you supply is an existing entry on the server. You can do this by listing the parent context of the entry or by using another tool such as the directory server's administrative console.
You may encounter some problems when trying to deploy applets that use JNDI classes.
- You get a
AppletSecurityExceptionwhen your applet tries to communicate with a directory server running on a machine different from the one from which the applet was loaded.
Cause: Your applet is not signed, so it can only connect to the machine from which it was loaded. Alternatively, if the applet is signed, the browser has not granted the applet permission to connect to the directory server machine.
Solution: If you want to allow your applet to connect to a directory server running on any machine, you need to sign your applet and all JNDI JAR files the applet will use. See Signing and Verifying JAR Files for information on signing jar files.
- You get a
AppletSecurityExceptionwhen your applet tries to use system properties to set environment properties.
Cause: Web browsers restrict access to system properties, and they throw a SecurityException if you try to read them.
Solution: If you need to get input for your applet, try using applet parameters.
- You get a
AppletSecurityExceptionwhen an applet running in Firefox tries to authenticate to an LDAP server using CRAM-MD5.
Cause: Firefox disables access to the java.security package. The LDAP provider uses the message digest functionality provided by java.security.MessageDigest to implement CRAM-MD5.
Solution: Use Java Plug-in.
Lesson: Advanced Topics for LDAP Users
LDAP The lessons in the tutorial provide details of the mapping between LDAP and JNDI. They also provide tips and tricks for accessing LDAP services via JNDI.
LDAP
X.500 is the CCITT standard for directory services and is part of the OSI service suite. The X.500 standard defines a protocol (among others) for client applications to access the X.500 directory, called the Directory Access Protocol (DAP). It is built on top of the Open Systems Interconnection (OSI) protocol stack.
The Internet community, recognizing the need for a service similar to X.500 but faced with a different underlying network infrastructure (TCP/IP instead of OSI), designed a new protocol based on the X.500 DAP protocol, called the Lightweight DAP, or LDAP. RFC 2251 defines LDAP as it is now known as Version 3 (or LDAP v3), an improvement over its predecessor LDAP v2, whose specification is in RFC 1777.
The goal of LDAP was to design a protocol that is easy to implement, with a particular focus on being able to build small and simple clients. It tries to achieve simplicity by making heavy use of strings and minimizing the use of structures where possible. For example, DNs are represented as strings in the protocol, as are attribute type names and many attribute values.
The protocol consists of a client sending requests to a server, and the server responding, though not necessarily in the order the requests were sent. Each request is tagged with an ID to allow matching of requests and responses. The protocol can run over TCP or UDP, though the TCP version is most commonly used.
Due to the focus on clients, the LDAP community has also defined standards for string representations of DNs (RFC 2553), search filters (RFC 1960), and attribute syntaxes (RFC 1778), as well as a C-based API (RFC 1823) and a URL format for accessing LDAP services (RFC 1959).
LDAP v3 supports internationalization, various authentication mechanisms, referrals, and a generic deployment mechanism. It allows new functionality to be added to the protocol without requiring changes to the protocol through the use of extensions and controls.
LDAP v3
Internationalization
Internationalization is handled through the international character set (ISO 10646) for representing protocol elements that are strings (like DNs). Version 3 also differs from version 2 in that it uses UTF-8 to encode its strings.
Authentication
In addition to anonymous and simple (clear text password) authentication, LDAP v3 uses the Simple Authentication and Security Layer (SASL) authentication framework (RFC 2222) to allow different authentication mechanisms to be used with LDAP. SASL specifies a challenge-response protocol in which data is exchanged between the client and the server for authentication purposes.
Several SASL mechanisms are currently defined: DIGEST-MD5, CRAM-MD5, Anonymous, External, S/Key, GSSAPI, and Kerberos v4. An LDAP v3 client can use any of these SASL mechanisms, provided the LDAP v3 server supports them. Additionally, new (not yet defined) SASL mechanisms can be used without having to change LDAP.
Referrals
A referral is information that a server sends back to a client, indicating that the requested information can be found at another location (possibly on another server). In LDAP v2, the server was supposed to handle referrals without returning them to the client. This was because handling referrals could be quite complex, thus leading to more complex clients. As servers were built and deployed, it was found that referrals were useful, but not all servers supported server-side referral handling. Thus, a way was found to improve the protocol to allow referrals to be returned. This was done by placing the referral in the error message of a "partial results" error response.
LDAP v3 explicitly supports referrals and allows the server to return referrals directly to the client. This lesson does not cover referrals, but you can always refer to the JNDI Tutorial for managing referrals in your applications.
Deployment
A common protocol such as LDAP is very useful for ensuring that all directory clients and servers "speak the same language." When many different directory client applications and directory servers are deployed in a network, it is also very useful that all these entities talk about the same objects.
A directory schema specifies the types of objects that the directory may have and the mandatory and optional attributes that each type of object may have, among other things. LDAP v3 defines a schema (RFC 2252 and RFC 2256) for objects common in networks, such as countries, localities, organizations, users/people, groups, and devices, based on the X.500 standard. It also defines a way for client applications to access the server's schema so that they can learn about the types of objects and attributes supported by a particular server.
LDAP v3 further defines a set of syntaxes for representing attribute values (RFC 2252). For writing Java applications that need to access the schema, refer to the JNDI Tutorial.
Extensions
In addition to the predefined set of operations such as "search" and "modify", LDAP v3 defines an "extended" operation. An "extended" operation takes a request as an argument and returns a response. The request contains an identifier identifying the request and the parameters of the request. The response contains the result of performing the request. An "extended" operation request/response pair is called an extension. For example, there can be an extension for starting TLS, which is a request from the client to the server to activate the Start TLS protocol.
These extensions can be standard (defined by the LDAP community) or proprietary (defined by a particular directory vendor). For writing applications that need to use extensions, refer to the JNDI Tutorial.
Controls
Another way to add new functionality is through the use of controls. LDAP v3 allows any operation to be modified through the use of controls. Any number of controls can be sent in an operation, and any number can be returned in its result. For example, you can send a sort control in a "search" operation, telling the server to sort the search results according to the "name" attribute.
Like extensions, these controls can be standard or proprietary. Standard controls are provided in the platform. For writing applications that need to use controls, refer to the JNDI Tutorial.
JNDI as an LDAP API
Both JNDI and LDAP models define a hierarchical namespace in which you can name objects. Each object in the namespace can have attributes that can be used to search for that object. At this high level, the two models are similar, so it is not surprising that JNDI maps well to LDAP.
Model
You can think of an LDAP entry as a JNDI DirContext. Each LDAP entry contains a name and a set of attributes, as well as an optional set of child entries. For example, the LDAP entry "o=JNDITutorial" might have its attributes "objectclass" and "o", and might have its child entries "ou=Groups" and "ou=People".
In JNDI, the LDAP entry "o=JNDITutorial" is represented as a context with the name "o=JNDITutorial", which has two child contexts named "ou=Groups" and "ou=People". The attributes of an LDAP entry are represented by the Attributes interface, and individual attributes are represented by the Attribute interface.
See the next part of this lesson for details on how to access LDAP operations through JNDI.
Names
As a result of federation, the names you supply to JNDI context methods can span multiple namespaces. These are called compound names. When using JNDI to access LDAP services, you should be aware that the slash character ("/") in string names has special meaning to JNDI. If an LDAP entry's name contains this character, you need to escape it (using the backslash character "\"). For example, an LDAP entry with the name "cn=O/R" must be presented as the string "cn=O\\/R" for use by JNDI context methods. For more information about names, see the JNDI Tutorial. The LdapName and Rdn classes simplify the creation and manipulation of LDAP names.
LDAP names used in the protocol are always fully qualified names that identify an entry starting from the root of the LDAP namespace (as defined by the server). Examples of fully qualified LDAP names are:
cn=Ted Geisel, ou=Marketing, o=Some Corporation, c=gb
cn=Vinnie Ryan, ou=People, o=JNDITutorial
In JNDI, however, names are always relative; that is, you always name an object relative to a context. For example, you could name the entry "cn=Vinnie Ryan" relative to a context named "ou=People, o=JNDITutorial". Or you could name the entry "cn=Vinnie Ryan, ou=People" relative to a context named "o=JNDITutorial". Or, you could create an initial context that points to the root of the LDAP server's namespace and name the entry "cn=Vinnie Ryan, ou=People, o=JNDITutorial".
In JNDI, you can also use LDAP URLs to name LDAP entries. See the discussion of LDAP URLs in the JNDI Tutorial.
How LDAP Operations Map to JNDI API
LDAP defines a set of operations or requests (see RFC 2251). In JNDI, these map to operations on the DirContext and LdapContext interfaces (which are subinterfaces of Context). For example, when the caller invokes a DirContext method, the LDAP service provider implements that method by sending an LDAP request to the LDAP server.
The following table shows how the operations in LDAP correspond to JNDI methods.
| LDAP Operation | Corresponding JNDI Method |
|---|---|
| Bind | The corresponding way to create an initial connection with an LDAP server in JNDI is to create an InitialDirContext. When the application creates the initial context, it provides client authentication information through environment properties. To change the authentication information for an existing context, use Context.addToEnvironment() and Context.removeFromEnvironment(). |
| Unbind | Context.close() is used to release the resources used by a context. It differs from the LDAP "unbind" operation in that, in a given service provider implementation, resources may be shared among contexts, so closing one context may not release all resources if those resources are being shared with another context. If your intent is to release all resources, make sure to close all contexts. |
| Search | The corresponding JNDI method is the overloaded DirContext.search() that accepts a search filter (RFC 2254). See the Filter examples. |
| Modify | The corresponding JNDI method is the overloaded DirContext.modifyAttributes() that accepts an array of DirContext.ModificationItem. See examples in the Modifying Attributes section. |
| add | The corresponding JNDI methods are DirContext.bind() and DirContext.createSubcontext(). You can use either method to add a new LDAP entry. Using bind(), you can specify not only a set of attributes for the new entry, but also the Java object to add along with the attributes. See the section Adding, Replacing, Binding with Attributes for examples. |
| delete | The corresponding JNDI methods are Context.unbind() and Context.destroySubcontext(). You can use either method to delete an LDAP entry. |
| modify DN/RDN | The corresponding JNDI method is Context.rename(). See the Renaming Objects section for more details. |
| compare | The corresponding JNDI operation is an appropriately constrained DirContext.search(). See the LDAP Compare section for examples. |
| abandon | When you close a context, all outstanding requests are abandoned. Similarly, when you close a NamingEnumeration, the corresponding LDAP "search" request is abandoned. |
| extended operation | The corresponding JNDI method is LdapContext.extendedOperation(). See the JNDI Tutorial for more details. |
How LDAP Error Codes Map to JNDI Exceptions
LDAP defines a set of status codes that are returned in LDAP responses sent by LDAP servers (see RFC 2251). In JNDI, error conditions are indicated as checked exceptions that are subclasses of NamingException. See the Naming Exceptions section for an overview of JNDI exception classes.
The LDAP service provider translates the LDAP status codes it receives from the LDAP server into appropriate NamingException subclasses. The following table shows the mapping of LDAP status codes to JNDI exceptions.
| LDAP Status Code | Meaning | Exception or Action |
|---|---|---|
| 0 | Success | Reports success. |
| 1 | Operations error | NamingException |
| 2 | Protocol error | CommunicationException |
| 3 | Time limit exceeded | TimeLimitExceededException |
| 4 | Size limit exceeded | SizeLimitExceededException |
| 5 | Compare false | Used by DirContext.search(). No exception generated. |
| 6 | Compare true | Used by DirContext.search(). No exception generated. |
| 7 | Authentication method not supported | AuthenticationNotSupportedException |
| 8 | Strong authentication required | AuthenticationNotSupportedException |
| 9 | Partial results being returned | Throws PartialResultException if the environment property "java.naming.referral" is set to "ignore" or the error content does not contain a referral. Otherwise, builds the referral using the content. |
| 10 | Referral encountered | If the environment property "java.naming.referral" is set to "ignore", it is ignored. If set to "throw", throws ReferralException. If set to "follow", the LDAP provider handles the referral. If the "java.naming.ldap.referral.limit" property is exceeded, throws LimitExceededException. |
| 11 | Administrative limit exceeded | LimitExceededException |
| 12 | Unavailable critical extension requested | OperationNotSupportedException |
| 13 | Confidentiality required | AuthenticationNotSupportedException |
| 14 | SASL bind in progress | Used internally by the LDAP provider during authentication. |
| 16 | No such attribute | NoSuchAttributeException |
| 17 | Undefined attribute type | InvalidAttributeIdentifierException |
| 18 | Inappropriate matching | InvalidSearchFilterException |
| 19 | Constraint violation | InvalidAttributeValueException |
| 20 | Attribute or value already in use | AttributeInUseException |
| 21 | Invalid attribute syntax | InvalidAttributeValueException |
| 32 | No such object | NameNotFoundException |
| 33 | Alias problem | NamingException |
| 34 | Invalid DN syntax | InvalidNameException |
| 35 | Is a leaf | Used by the LDAP provider; usually no exception generated. |
| 36 | Alias dereferencing problem | NamingException |
| 48 | Inappropriate authentication | AuthenticationNotSupportedException |
| 49 | Invalid credentials | AuthenticationException |
| 50 | Insufficient access rights | NoPermissionException |
| 51 | Busy | ServiceUnavailableException |
| 52 | Unavailable | ServiceUnavailableException |
| 53 | Unwilling to perform | OperationNotSupportedException |
| 54 | Loop detected | NamingException |
| 64 | Naming violation | InvalidNameException |
| 65 | Object class violation | SchemaViolationException |
| 66 | Not allowed on non-leaf | ContextNotEmptyException |
| 67 | Not allowed on RDN | SchemaViolationException |
| 68 | Entry already exists | NameAlreadyBoundException |
| 69 | Object class modification prohibited | SchemaViolationException |
| 71 | Affects multiple DSAs | NamingException |
| 80 | Other | NamingException |
Security
LDAP services provide a general-purpose directory service. It can be used to store various kinds of information. All LDAP servers have a system for controlling who can read and update the information in the directory.
To access an LDAP service, the LDAP client first must authenticate itself to the service. That is, it must tell the LDAP server who is going to access the data, so that the server can decide what the client is allowed to see and do. If the client successfully authenticates to the LDAP server, then when the server subsequently receives a request from the client, it checks whether the client is allowed to perform that request. This process is called access control.
The LDAP standard suggests ways in which an LDAP client can authenticate to an LDAP server (RFC 2251 and RFC 2829). These are summarized in the LDAP Authentication section and the Authentication Mechanisms section. This lesson also contains descriptions of how to use anonymous, simple, and SASL authentication mechanisms.
Different LDAP server implementations support access control in different ways. This lesson does not discuss this.
Another security aspect of LDAP services is support for using a secure channel to communicate with clients, such as for sending and receiving attributes containing secret information like passwords and keys. LDAP servers use SSL for this purpose. This lesson also shows how to use SSL with the LDAP service provider.
Authentication for LDAP
In LDAP, authentication information is supplied in the "bind" operation. In LDAP v2, the client establishes a connection with the LDAP server by sending the "bind" operation containing the authentication information.
In LDAP v3, this operation has the same purpose but is optional. A client that sends LDAP requests without performing the "bind" operation is considered an anonymous client (see the Anonymous section for details). In LDAP v3, the "bind" operation can be sent at any time during the connection, possibly multiple times. The client can send a "bind" request in the middle of the connection to change its identity. If the request succeeds, any outstanding requests on the connection using the old identity are discarded, and the connection is associated with the new identity.
The authentication information supplied in the "bind" operation depends on the authentication mechanism chosen by the client. See Authentication Mechanisms for a discussion of authentication mechanisms.
LDAP Authentication Using JNDI
In JNDI, authentication information is specified in environment properties. When you create an initial context using the InitialDirContext class (or its superclasses or subclasses), you provide a set of environment properties, some of which may contain authentication information. You can use the following environment properties to specify authentication information.
-
Context.SECURITY_AUTHENTICATION("java.naming.security.authentication").Specifies the authentication mechanism to use. For the LDAP service provider in the JDK, this can be one of the following strings: "none", "simple", sasl_mech, where sasl_mech is a space-separated list of SASL mechanism names. See Authentication Mechanisms for a description of these strings.
-
Context.SECURITY_PRINCIPAL("java.naming.security.principal").Specifies the name of the user/program performing the authentication, depending on the value of the
Context.SECURITY_AUTHENTICATIONproperty. See the next sections of this lesson for details and examples. -
Context.SECURITY_CREDENTIALS("java.naming.security.credentials").Specifies the credentials of the user/program performing the authentication, depending on the value of the
Context.SECURITY_AUTHENTICATIONproperty. See the next sections of this lesson for details and examples.
When you create an initial context, the underlying LDAP service provider extracts the authentication information from these environment properties and passes it to the server using the LDAP "bind" operation.
The following example shows how a client authenticates to an LDAP server by using a simple clear text password.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
Using Different Authentication Information for a Context
If you want to use different authentication information for an existing context, you can update the environment properties containing the authentication information using Context.addToEnvironment() and Context.removeFromEnvironment(). Subsequent method calls on the context will use the new authentication information to communicate with the server.
The following example shows how to change the authentication information of a context to "none" after the context has been created.
// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
// Change to using no authentication
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "none");
// ... do something useful with ctx
Authentication Failure
Authentication can fail for a variety of reasons. For example, if incorrect authentication information is supplied, such as an incorrect password or principal name, an AuthenticationException is thrown.
Here is a variant of the example. This time, an incorrect password causes authentication to fail.
// Authenticate as S. User and give an incorrect password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "notmysecret");
This produces the following output.
javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
...
Because different servers support different authentication mechanisms, you might request an authentication mechanism that the server does not support. In such a case, an AuthenticationNotSupportedException is thrown.
Here is a variant of the example. This time, an unsupported authentication mechanism ("custom") causes authentication to fail.
// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "custom");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
This produces the following output.
javax.naming.AuthenticationNotSupportedException: custom
...
Authentication Mechanisms
Different versions of LDAP support different types of authentication. LDAP v2 defines three types of authentication: anonymous, simple (clear text password), and Kerberos v4.
LDAP v3 supports anonymous, simple, and SASL authentication. SASL stands for Simple Authentication and Security Layer (RFC 2222). It specifies a challenge-response protocol in which data is exchanged between the client and the server for authentication and to establish a security layer for subsequent communication. By using SASL, LDAP can support any type of authentication negotiated by the LDAP client and server.
This lesson contains descriptions of how to authenticate using anonymous, simple, and SASL authentication.
Specifying the Authentication Mechanism
The authentication mechanism is specified using the Context.SECURITY_AUTHENTICATION environment property. This property can have one of the following values.
| Property Name | Property Value |
|---|---|
| sasl_mech | A space-separated list of SASL mechanism names. Use one of the listed SASL mechanisms (e.g., "CRAM-MD5" for the CRAM-MD5 SASL mechanism described in RFC 2195). |
none |
No authentication (anonymous) |
simple |
Weak authentication (clear text password) |
Default Mechanism
If the client does not specify any authentication environment properties, the default authentication mechanism is "none". The client will then be treated as an anonymous client.
If the client specifies authentication information without explicitly specifying the Context.SECURITY_AUTHENTICATION property, the default authentication mechanism is "simple".
Anonymous
As just stated, if no authentication environment properties are set, then the default authentication mechanism is "none". If the client sets the Context.SECURITY_AUTHENTICATION environment property to "none", then the authentication mechanism is "none", and all other authentication environment properties are ignored. You would only need to do this if you explicitly want to ignore any other authentication properties that might have been set. In either case, the client will be considered an anonymous client. This means that the server does not know or care who the client is and will allow the client to access (read and update) any data that has been configured to be accessible by any unauthenticated client.
Because all directory examples in the Naming and Directory Operations lesson do not set any authentication environment properties, they all use anonymous authentication.
Here is an example that explicitly sets the Context.SECURITY_AUTHENTICATION property to "none" (though doing so is not strictly necessary because it is the default).
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Use anonymous authentication
env.put(Context.SECURITY_AUTHENTICATION, "none");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
Simple
Simple authentication consists of sending the client's (user's) fully qualified DN and the client's clear text password to the LDAP server (see RFC 2251 and RFC 2829). This mechanism has a security problem because the password can be read from the network. To avoid exposing the password in this way, you can use the simple authentication mechanism over an encrypted channel, such as SSL, provided the LDAP server supports it.
Both LDAP v2 and v3 support simple authentication.
To use the simple authentication mechanism, you must set three authentication environment properties as follows.
Context.SECURITY_AUTHENTICATION.
Set to "simple".
Context.SECURITY_PRINCIPAL.
Set to the fully qualified DN of the entity being authenticated (e.g., "cn=S. User, ou=NewHires, o=JNDITutorial"). Its type is java.lang.String.
Context.SECURITY_CREDENTIALS.
Set to the principal's password (e.g., "mysecret"). Its type can be java.lang.String, char array (char[]), or byte array (byte[]). If the password is a java.lang.String or char array, it is encoded using UTF-8 for LDAP v3 and ISO-Latin-1 for LDAP v2 when transmitted to the server. If the password is a byte[], it is transmitted as-is to the server.
See the earlier examples in this section that demonstrate using simple authentication.
Note: If you supply an empty string, an empty byte/char array, or null to the Context.SECURITY_CREDENTIALS environment property, the authentication mechanism will be "none". This is because LDAP requires that the password for simple authentication cannot be empty. If no password is provided, the protocol automatically converts the authentication to "none".
SASL
The LDAP v3 protocol uses SASL to support pluggable authentication. This means that an LDAP client and server can negotiate and use authentication mechanisms that may be non-standard and/or customized, depending on the level of protection required by the client and server. The LDAP v2 protocol does not support SASL.
Several SASL mechanisms are currently defined:
- Anonymous (RFC 2245)
- CRAM-MD5 (RFC 2195)
- Digest-MD5 (RFC 2831)
- External (RFC 2222)
- Kerberos V4 (RFC 2222)
- Kerberos V5 (RFC 2222)
- SecurID (RFC 2808)
- S/Key (RFC 2222)
SASL Mechanisms Supported by LDAP Servers
Among the list above, popular LDAP servers like Oracle, OpenLDAP, and Microsoft support External, Digest-MD5, and Kerberos V5. RFC 2829 proposes Digest-MD5 as the mandatory default mechanism for LDAP v3 servers.
Here is a simple program that finds the list of SASL mechanisms supported by an LDAP server.
// Create initial context
DirContext ctx = new InitialDirContext();
// Read supportedSASLMechanisms from root DSE
Attributes attrs = ctx.getAttributes(
"ldap://localhost:389", new String[]{"supportedSASLMechanisms"});
Running this program against a server that supports the External SASL mechanism produces the following output.
{supportedsaslmechanisms=supportedSASLMechanisms:
EXTERNAL, GSSAPI, DIGEST-MD5}
Specifying the Authentication Mechanism
To use a particular SASL mechanism, you specify its IANA-registered mechanism name in the Context.SECURITY_AUTHENTICATION environment property. You can also specify a list of mechanisms for the LDAP provider to try. You do this by specifying an ordered, space-separated list of mechanism names. The LDAP provider will use the first mechanism for which it finds an implementation.
Here is an example that asks the LDAP provider to try to get an implementation of the DIGEST-MD5 mechanism, and if not available, use the GSSAPI implementation.
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");
You can get this list of authentication mechanisms from your application's user. Or you can query the LDAP server for it using a call similar to the one shown earlier. The LDAP provider itself does not query the server for this information. It merely tries to locate and use an implementation of the specified mechanisms.
The LDAP provider in the platform has built-in support for the External, Digest-MD5, and GSSAPI (Kerberos v5) SASL mechanisms. You can add support for other mechanisms.
Specifying Input for the Authentication Mechanism
Some mechanisms, such as External, do not require any additional input; the mere mechanism name is sufficient for authentication. The External example shows how to use the External SASL mechanism.
Most other mechanisms require some additional input. The type of input may vary depending on the mechanism. The following are common input requirements for mechanisms.
- Authentication ID. The identity of the entity performing the authentication.
- Authorization ID. The identity of the entity for which access control checks should be performed if authentication succeeds.
- Authentication credentials. For example, a password or a key.
The authentication and authorization IDs may differ if a program (such as a proxy server) authenticates on behalf of another entity. The authentication ID is specified using the Context.SECURITY_PRINCIPAL environment property. Its type is java.lang.String.
The password/key for the authentication ID is specified using the Context.SECURITY_CREDENTIALS environment property. Its type is java.lang.String, char array (char[]), or byte array (byte[]). If the password is a byte array, its converted to a char array using UTF-8 encoding.
If the "java.naming.security.sasl.authorizationId" property is set, its value is used as the authorization ID. Its value must be of type java.lang.String. By default, an empty string is used as the authorization ID, which instructs the server to derive the authorization ID from the client's authentication credentials.
The Digest-MD5 example shows how to use the Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS properties for Digest-MD5 authentication.
If a mechanism requires input beyond what has been described, then you need to define a callback object for the mechanism to use, which you can see in the JNDI Tutorial callback example. The next part of this lesson discusses using the SASL Digest-MD5 authentication mechanism. The SASL policies, GSS API (Kerberos v5), and CRAM-MD5 mechanisms are covered in the JNDI Tutorial.
Digest-MD5
Digest-MD5 authentication is the required authentication mechanism for LDAP v3 servers (RFC 2829). Because the use of SASL is part of LDAP v3 (RFC 2251), servers that support only LDAP v2 do not support Digest-MD5.
The Digest-MD5 mechanism is described in RFC 2831. It is based on HTTP Digest Authentication (RFC 2251). In Digest-MD5, the LDAP server sends data to the LDAP client containing various authentication options along with a special token. The client responds by sending an encrypted response indicating the authentication options it has selected. The response is encrypted to prove that the client knows its password. The LDAP server then decrypts and verifies the client's response.
To use the Digest-MD5 authentication mechanism, you must set the authentication environment properties as follows.
Context.SECURITY_AUTHENTICATION.
Set to the string "DIGEST-MD5".
Context.SECURITY_PRINCIPAL.
Set to the principal name. This is a server-specific format. Some servers support a login user id format, such as the format defined in the UNIX or Windows login screen. Others accept a distinguished name. Still others use the authorization id format defined in RFC 2829. In that RFC, the name should be the string "dn:" followed by the fully qualified DN of the entity being authenticated, or the string "u:" followed by the user id. Some servers accept multiple formats. Examples of some formats are "cuser", "dn: cn=C. User, ou=NewHires, o=JNDITutorial", and "u: cuser". The data type of this property must be java.lang.String.
Context.SECURITY_CREDENTIALS.
Set to the principal's password (e.g., "mysecret"). Its type can be java.lang.String, char array (char[]), or byte array (byte[]). If the password is a java.lang.String or char[], it is encoded using UTF-8 for transmission to the server. If the password is a byte[], it is transmitted as-is.
The following example shows how a client authenticates to an LDAP server using Digest-MD5.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Authenticate as C. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL,
"dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
Note: Oracle Directory Server, v5.2 supports the Digest-MD5 authentication mechanism for users with clear text passwords. You must set the password encryption mode before creating the user. If you have already created the user, delete and recreate it. To set the password encryption mode using the admin console, select the "Configuration" tab and the "Data" node. In the "Password" pane, select the "No encryption (CLEAR)" option for "Password encryption". The server accepts simple user names (i.e., the value of the "uid" attribute of an entry) and the "dn:" format for user names. See the server's documentation for details.
Specifying the Realm
The realm defines the namespace from which the authentication entity (the value of the Context.SECURITY_PRINCIPAL property) is chosen. A server may have multiple realms. For example, a university server might be configured to have two realms, one for student users and another for faculty users. Realm configuration is done by the directory administrator. Some directories have a single default realm. For example, Oracle Directory Server, v5.2 uses the fully qualified hostname of the machine as the default realm.
In Digest-MD5 authentication, you must authenticate to a specific realm. You can specify the realm using the following authentication environment property. If you do not specify a realm, any one of the realms offered by the server will be used.
java.naming.security.sasl.realm
Set to the principal's realm. This is a deployment-specific and/or server-specific case-sensitive string. It identifies the realm or domain from which the principal name (Context.SECURITY_PRINCIPAL) should be chosen. If this realm does not match one of the realms offered by the server, authentication fails.
The following example shows how to set the environment properties to authenticate using Digest-MD5 and specify the realm. To get this example to work in your environment, you must change the source code so that the realm value reflects the configuration on your directory server.
// Authenticate as C. User and password "mysecret" in realm "JNDITutorial"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL,
"dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
env.put("java.naming.security.sasl.realm", "JNDITutorial");
If you need to use privacy protection and other SASL properties, see the JNDI Tutorial.
SSL and Custom Sockets
In addition to SASL authentication, most LDAP servers allow their services to be accessed over SSL. SSL is particularly useful for LDAP v2 servers because the v2 protocol does not support SASL authentication.
Servers that enable SSL typically support SSL in two ways. In the most basic way, the server supports an SSL port in addition to a plain (unprotected) port. Another way servers support SSL is through the use of the "Start TLS extension" (RFC 2830). This option is only available with LDAP v3 servers and is described in that section.
Using the SSL Socket Property
By default, the LDAP service provider in the JDK uses plain sockets when communicating with LDAP servers. To request the use of SSL sockets, set the Context.SECURITY_PROTOCOL property to "ssl".
In the following example, the LDAP server offers SSL on port 636. To run this program, you must enable SSL on port 636 on your LDAP server. This procedure is typically performed by the directory administrator.
Server requirements: The LDAP server must be set up with an X.509 SSL server certificate and have SSL enabled. Typically, you must first obtain a signed certificate for the server from a certificate authority (CA). Then, follow the directory vendor's instructions to enable SSL. Different vendors have different tools to do this.
For Oracle Directory Server, v5.2, use the "Manage Certificates" tool in the admin console to generate a certificate signing request (CSR). Submit the CSR to a CA to obtain an X.509 SSL server certificate. Using the admin console, add the certificate to the server's certificate list. If the CA's certificate is not already in the server's list of trusted CAs, also install the CA's certificate. Enable SSL by using the "Configuration" tab in the admin console. Select the server in the left pane. In the right pane, select the "Encryption" tab. Check the "Enable SSL for this server" and "Use this cipher family: RSA" checkboxes, making sure the server certificate you added is in the certificate list.
Client requirements: You need to ensure that the client trusts the LDAP server you will be using. You must install the server's certificate (or its CA's certificate) in the JRE's trusted certificate database. Here is an example.
# cd *JAVA_HOME*/lib/security
# keytool -import -file server_cert.cer -keystore jssecacerts
For information on how to use security tools, see the Security Guide. For information on JSSE, see the Java Secure Socket Extension (JSSE) Reference Guide.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");
// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires,o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
Note: If you use SSL to connect to a server on a port that is not using SSL, your program will hang. Similarly, if you use plain sockets to connect to a server's SSL socket, your application will hang. This is a characteristic of the SSL protocol.
Using LDAPS URLs
By requesting the use of SSL via the Context.SECURITY_PROTOCOL property, you can also request the use of SSL by using an LDAPS URL. An LDAPS URL is similar to an LDAP URL, except that the URL scheme is "ldaps" instead of "ldap". It specifies that SSL be used when communicating with the LDAP server.
In the following example, the LDAP server offers SSL on port 636. To run this program, you must enable SSL on port 636 on your LDAP server.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// Specify LDAPS URL
env.put(Context.PROVIDER_URL, "ldaps://localhost:636/o=JNDITutorial");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL,
"cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
LDAPS URLs can be used anywhere that LDAP URLs are accepted. See the JNDI Tutorial for details on LDAP and LDAPS URLs.
Client Authentication: SSL Using the External SASL Mechanism
SSL provides authentication and other security services at a lower level than LDAP. If authentication has already been done over SSL, the LDAP layer can use that authentication information from SSL by using the External SASL mechanism.
The following example is similar to the previous SSL example, except that instead of using simple authentication, it uses External SASL authentication. By using External, you do not need to supply any principal or password information because it is obtained from SSL.
Server requirements: This example requires that the LDAP server allow certificate-based client authentication. Additionally, the LDAP server must trust the received client certificate (of the CA) and must be able to map the owner distinguished name in the client certificate to a principal it knows about. Follow your directory vendor's instructions to perform these tasks.
Client requirements: This example requires that the client have an X.509 SSL client certificate. Additionally, the certificate must be stored as the first key entry in a keystore file. If this entry is password-protected, it must have the same password as the keystore. For more information on JSSE keystores, see the Java Secure Socket Extension (JSSE) Reference Guide.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");
// Principal and credentials will be obtained from the connection
env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL");
// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
...
To run this program so that it authenticates using the client certificate, you must provide (as system properties) the location and password of the keystore containing the client certificate. Here is an example of running the program.
java -Djavax.net.ssl.keyStore=MyKeystoreFile \
-Djavax.net.ssl.keyStorePassword=mysecret \
External
If you do not provide a keystore, the program will run using anonymous authentication because there is no client credential on SSL.
This example demonstrates the most basic way to implement certificate-based client authentication. A more advanced approach can be implemented in a more flexible way by writing and using a custom socket factory that accesses the client certificate, perhaps by using an LDAP directory. The next section shows how to use custom socket factories in JNDI applications.
Using Custom Sockets
When using SSL, by default the LDAP provider will use the socket factory, javax.net.ssl.SSLSocketFactory, for creating SSL sockets to communicate with the server, using the default JSSE configuration. JSSE can be customized in many ways; see the Java Secure Socket Extension (JSSE) Reference Guide for details. However, sometimes these customizations are not enough, and you need more control over the SSL sockets or sockets in general that the LDAP service provider uses. For example, you might need a socket that can bypass a firewall, or a JSSE socket that uses a non-default caching/retrieval policy for managing its trust and key stores. To set the socket factory implementation used by the LDAP service provider, set the "java.naming.ldap.factory.socket" property to the fully qualified class name of the socket factory. This class must implement the javax.net.SocketFactory abstract class and provide an implementation of the getDefault() method that returns an instance of the socket factory. See the Java Secure Socket Extension (JSSE) Reference Guide.
Here is an example of a custom socket factory that produces plain sockets.
public class CustomSocketFactory extends SocketFactory {
public static SocketFactory getDefault() {
System.out.println("[acquiring the default socket factory]");
return new CustomSocketFactory();
}
...
}
Note that this example creates a new instance of CustomSocketFactory each time a new LDAP connection is made. This may be appropriate for some applications and socket factories. If you want to reuse the same socket factory, getDefault() should return a singleton.
To use this custom socket factory in a JNDI program, set the "java.naming.ldap.factory.socket" property, as shown in the following example.
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Specify the socket factory
env.put("java.naming.ldap.factory.socket", "CustomSocketFactory");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// ... do something useful with ctx
The "java.naming.ldap.factory.socket" property can be used to set the socket factory on a per-context basis. Another way to control the sockets used by the LDAP service provider is by using java.net.Socket.setSocketImplFactory() to set the socket factory for all sockets used throughout the program. This approach is less flexible because it affects all socket connections, not just LDAP connections, so it should be used with caution.
More LDAP Operations
The rest of this LDAP lesson introduces the ability that JNDI provides to perform some interesting LDAP operations.
Renaming Objects
You can use Context.rename() to rename objects in a directory. In LDAP v2, this corresponds to the "modify RDN" operation, which renames an entry within the same context (i.e., renames a sibling). In LDAP v3, this corresponds to the "modify DN" operation, which is similar to "modify RDN" except that the old and new entries need not be in the same context. You can use Context.rename() to rename leaf entries or internal nodes. The example shown in the Naming and Directory Operations lesson renamed a leaf entry. The following code renames an internal node from "ou=NewHires" to "ou=OldHires":
ctx.rename("ou=NewHires", "ou=OldHires");
Note: Oracle Directory Server v5.2 does not support renaming internal nodes. If you run this example, you will get a ContextNotEmptyException.
Renaming an Entry to a Different Part of the DIT
With LDAP v3, you can rename an entry to a different part of the DIT. To achieve this with Context.rename(), you must use a context that is a common ancestor of both the new and old entries. For example, to rename "cn=C. User, ou=NewHires, o=JNDITutorial" to "cn=C. User, ou=People, o=JNDITutorial", you would use the context named by "o=JNDITutorial". The following example demonstrates this. If you try to run this example on an LDAP v2 server, you will get an InvalidNameException because version 2 does not support this.
ctx.rename("cn=C. User, ou=NewHires", "cn=C. User, ou=People");
Note: Oracle Directory Server v5.2 does not support renaming with different parents. If you run this example with that server, you will get an OperationNotSupportedException (indicating "protocol error").
Preserving the Old Name Attribute
In LDAP, when you rename an entry, you have the option to keep the old RDN of the entry as an attribute of the updated entry. For example, if you rename an entry "cn=C. User" to "cn=Claude User", you can specify whether you want to keep the old RDN "cn=C. User" as an attribute.
To specify whether you want to preserve the old name attribute when using Context.rename(), use the "java.naming.ldap.deleteRDN" environment property. If the value of this property is "true" (the default), the old RDN is removed. If its value is "false", the old RDN is kept as an attribute of the updated entry. A complete example is here.
// Set the property to keep RDN
env.put("java.naming.ldap.deleteRDN", "false");
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Perform the rename
ctx.rename("cn=C. User, ou=NewHires", "cn=Claude User,ou=NewHires");
LDAP Compare
The LDAP "compare" operation allows a client to ask the server whether the server has an attribute/value pair for a specified entry. This allows the server to keep certain attribute/value pairs confidential (i.e., not generally accessible to "search" operations) while still allowing clients limited use of them. For example, some servers might use this feature for passwords, though it is not secure for clients to pass clear text passwords in the "compare" operation itself.
To achieve this in JNDI, use appropriately constrained arguments to the following methods:
search(Name name, String filter, SearchControls ctls)search(Name name, String filterExpr, Object[] filterArgs, SearchControls ctls)
- The filter must be of the form "(name = value)". No wildcards can be used.
- The search scope must be
SearchControls.OBJECT_SCOPE. - You must request that no attributes be returned. If these conditions are not met, then these methods will use the LDAP "search" operation instead of the LDAP "compare" operation.
Here is an example that will cause the LDAP "compare" operation to be used.
// Value of the attribute
byte[] key = {(byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64,
(byte)0x65, (byte)0x66, (byte)0x67};
// Set up the search controls
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(new String[0]); // Return no attrs
ctls.setSearchScope(SearchControls.OBJECT_SCOPE); // Search object only
// Invoke search method that will use the LDAP "compare" operation
NamingEnumeration answer = ctx.search("cn=S. User, ou=NewHires",
"(mySpecialKey={0})",
new Object[]{key}, ctls);
If the comparison succeeds, the result enumeration will contain a single item with an empty name and no attributes.
Search Results
When you use the search methods in the DirContext interface, you get back a NamingEnumeration. Each item in the NamingEnumeration is a SearchResult that contains the following information:
- Name
- Object
- Class Name
- Attributes
Name
Each SearchResult contains the name of the LDAP entry that satisfies the search filter. You can get the entry's name by using getName(). This method returns a compound name of the LDAP entry relative to the target context. The target context is the context to which the name parameter resolves. In LDAP terms, the target context is the base object of the search. Here is an example.
NamingEnumeration answer = ctx.search("ou=NewHires",
"(&(mySpecialKey={0}) (cn=*{1}))", // Filter expression
new Object[]{key, name}, // Filter arguments
null); // Default search controls
In this example, the target context is named by "ou=NewHires". The names in the SearchResult in answer are relative to "ou=NewHires". For example, if getName() returns "cn=J. Duke", then the name relative to ctx would be "cn=J. Duke, ou=NewHires".
If you perform a search with SearchControls.SUBTREE_SCOPE or SearchControls.OBJECT_SCOPE, and the target context itself satisfies the search filter, the name returned will be "" (the empty name), because that is the name relative to the target context.
That is not all. If the search involves referrals (see the JNDI Tutorial) or dereferencing aliases (see the JNDI Tutorial), then the corresponding SearchResult will have names that are not relative to the target context. Instead, they will be URLs that directly refer to the entry. To determine whether the name returned by getName() is relative or absolute, use isRelative(). If this method returns true, the name is relative to the target context; if it returns false, the name is a URL.
If the name is a URL, and you need to use the URL, you can pass it to an initial context that understands URLs (see the JNDI Tutorial).
If you need to get the full DN of the entry, you can use NameClassPair.getNameInNamespace().
Object
If the search was requested to return the entry's object (by calling SearchControls.setReturningObjFlag() with true), then the SearchResult will contain an object representing the entry. To retrieve this object, call getObject(). If a java.io.Serializable, Referenceable, or Reference object was previously bound to the LDAP name, that object is reconstructed using the attributes from the entry (see examples in the JNDI Tutorial). Otherwise, a DirContext instance representing the LDAP entry is created from the attributes in the entry. In either case, the LDAP provider calls DirectoryManager.getObjectInstance() on the object and returns the result.
Class Name
If the search was requested to return the entry's object, the class name is derived from the returned object. If the search request includes retrieving the LDAP entry's "javaClassName" attribute, the class name is the value of that attribute. Otherwise, the class name is "javax.naming.directory.DirContext". The class name is retrieved via getClassName().
Attributes
When performing a search, you can select which attributes to return by either providing arguments to one of the search() methods or by setting the search controls using SearchControls.setReturningAttributes(). If no attributes are explicitly specified, all attributes of the LDAP entry are returned. To specify that no attributes be returned, you must pass an empty array (new String[0]).
To retrieve the attributes of an LDAP entry, call getAttributes() on the SearchResult.
Response Controls
See the "Controls and Extensions" lesson in the JNDI Tutorial for details on how to retrieve the response controls of a search result.
LDAP Unsolicited Notifications
LDAP v3 (RFC 2251) defines unsolicited notifications, messages that an LDAP server sends to a client without the client having initiated them. In JNDI, unsolicited notifications are represented by the UnsolicitedNotification interface.
Because the server sends unsolicited notifications asynchronously, you use the same event model as receiving notifications about namespace changes and object content changes. You register your interest in receiving unsolicited notifications by registering an UnsolicitedNotificationListener on an EventContext or EventDirContext.
Here is an example showing the implementation of an UnsolicitedNotificationListener.
public class UnsolListener implements UnsolicitedNotificationListener {
public void notificationReceived(UnsolicitedNotificationEvent evt) {
System.out.println("received: " + evt);
}
public void namingExceptionThrown(NamingExceptionEvent evt) {
System.out.println(">>> UnsolListener got an exception");
evt.getException().printStackTrace();
}
}
Here is an example showing how to register an implementation of UnsolicitedNotificationListener with an event source. Note that only the listener parameter in EventContext.addNamingListener() is relevant to unsolicited notifications. The name and scope parameters are irrelevant for unsolicited notifications.
// Get the event context for registering the listener
EventContext ctx = (EventContext)
(new InitialContext(env).lookup("ou=People"));
// Create the listener
NamingListener listener = new UnsolListener();
// Register the listener with the context (all targets equivalent)
ctx.addNamingListener("", EventContext.ONELEVEL_SCOPE, listener);
When running this program, you need to point it to an LDAP server that can generate unsolicited notifications and cause the server to issue one. Otherwise, the program will exit quietly after a minute.
A listener that implements UnsolicitedNotificationListener can also implement other NamingListener interfaces, such as NamespaceChangeListener and ObjectChangeListener.
Connection Management
JNDI provides a high-level interface for accessing naming and directory services. The mapping between a JNDI Context instance and the underlying network connection may not be one-to-one. Service providers are free to share and reuse connections as long as the interface semantics are preserved. Application developers typically do not need to know the details of how a Context instance creates and uses connections. These details are useful when a developer needs to tune the program.
This lesson describes how the LDAP service provider uses connections. It describes when a connection is created and how to specify special connection parameters, such as multiple servers and connection timeouts. This lesson also shows how to dynamically discover and use LDAP servers in a supported network environment.
Connections that are created must eventually be closed. This lesson contains a section describing how connection closing occurs on both the client and server side.
Finally, this lesson shows you how to use connection pooling to make applications that use many short-lived connections more efficient.
Note: The information provided in this lesson applies only to the LDAP service provider in the JDK. LDAP service providers from other vendors may not use the same connection management strategy.
Creating
There are several ways a connection is created. The most common way is by creating an initial context. When you create an InitialContext, InitialDirContext, or InitialLdapContext using the LDAP service provider, a connection is established immediately with the target LDAP server named in the Context.PROVIDER_URL property. A new LDAP connection is created each time an initial context is created. See the Pooling section for information on how to change this behavior.
If the property value contains multiple URLs, each URL is tried in turn until a connection is successfully created. The property value is then updated to the successful URL. See the JNDI Tutorial for an example of creating an initial context with a list of URLs.
There are three more ways to create a connection directly.
-
By passing a URL as the name parameter to the initial context. When an LDAP or LDAPS URL is passed as the name parameter to an initial context, the information in the URL is used to create a new connection to the LDAP server, regardless of whether the initial context instance itself is connected to an LDAP server. In fact, the initial context may not be connected to any server. See the JNDI Tutorial for more information on how to use URLs as names.
-
Another way to create a connection is by using a
Reference. When aReferencecontaining an LDAP or LDAPS URL is passed toNamingManager.getObjectInstance()orDirectoryManager.getObjectInstance(), a new connection is created using the information specified in the URL. -
Finally, when referrals are followed manually or automatically, the information in the referral is used to create a new connection. See the JNDI Tutorial for information on referrals.
Sharing Connections
Context instances derived from a Context instance and NamingEnumerations will share the same connection until a change is made to one of the Context instances that makes sharing no longer possible. For example, if you call Context.lookup(), Context.listBindings(), or DirContext.search() from an initial context and obtain other Context instances, all these Context instances will share the same connection.
Here is an example.
// Create initial context
DirContext ctx = new InitialDirContext(env);
// Get a copy of the same context
Context ctx2 = (Context)ctx.lookup("");
// Get a child context
Context ctx3 = (Context) ctx.lookup("ou=NewHires");
In this example, ctx, ctx2, and ctx3 will share the same connection.
Sharing occurs regardless of how the Context instance was produced. For example, Context instances obtained by following a referral will share the same connection as the referral.
When you change the environment properties of a Context instance that are related to the connection, such as the user's principal name or credentials, the Context instance on which you make these changes will get its own connection (if the connection is shared). Future Context instances derived from this Context instance will share this new connection. Context instances that previously shared the old connection are unaffected (i.e., they continue to use the old connection).
Here is an example using two connections.
// Create initial context (first connection)
DirContext ctx = new InitialDirContext(env);
// Get a copy of the same context
DirContext ctx2 = (DirContext)ctx.lookup("");
// Change authentication properties in ctx2
ctx2.addToEnvironment(Context.SECURITY_PRINCIPAL,
"cn=C. User, ou=NewHires, o=JNDITutorial");
ctx2.addToEnvironment(Context.SECURITY_CREDENTIALS, "mysecret");
// Method on ctx2 will use new connection
System.out.println(ctx2.getAttributes("ou=NewHires"));
ctx2 initially shares the same connection with ctx. But when its principal and password properties are changed, it can no longer use ctx's connection. The LDAP provider will automatically create a new connection for ctx2.
Similarly, if you use LdapContext.reconnect() to change the connection controls of a Context instance, if the connection is being shared, the Context instance will get its own connection.
If a Context instance's connection is not shared (i.e., no Context is derived from it), changes to its environment or connection controls will not result in a new connection being created. Instead, any changes related to the connection will be applied to the existing connection.
Connection Timeout
Not all connection creations succeed. If the LDAP provider cannot establish a connection within a certain timeout period, it aborts the connection attempt. By default, this timeout period is the network (TCP) timeout value, which is about a few minutes. To change the timeout period, you can use the "com.sun.jndi.ldap.connect.timeout" environment property. The value of this property is the string representation of an integer representing the connection timeout in milliseconds.
Here is an example.
// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Specify timeout to be 5 seconds
env.put("com.sun.jndi.ldap.connect.timeout", "5000");
// Create initial context
DirContext ctx = new InitialDirContext(env);
// do something useful with ctx
In this example, if the connection cannot be created within 5 seconds, an exception is thrown.
If the Context.PROVIDER_URL property contains multiple URLs, the provider will use the timeout for each URL. For example, if there are 3 URLs and the timeout has been specified as 5 seconds, the provider will wait up to 15 seconds total.
See the Connection Pooling section for how this property affects connection pooling.