- JNDI Basics
- Directory Operations
- Using LDAP with JNDI
- LDAP Classes and Attributes
- Troubleshooting
LDAP Classes and Attributes
Although LDAP entries are really just a collection of attributes, LDAP has the concept of classes. Every LDAP entry has an attribute called objectClass that lists the class hierarchy for an object. Not only does objectClass contain the object's class, it must contain the entire list of superclasses all the way back to the top class. Fortunately, the classes aren't nested too deeply, so the objectClass list is usually fairly small.
One other thing to keep in mind: The class hierarchy doesn't dictate the structure of the directory tree. A node in the directory tree can contain one of its superclasses as a child.
Table 18.1 lists some of the common LDAP classes. The complete set of classes is defined in the standard RFC2256, which you can view at http://www.ietf.org/rfc/rfc2256.txt.
Table Some Common LDAP Classes
Classname | Parent Class | Required Attribute(s) |
---|---|---|
top | None | ObjectClass |
country | top | c |
locality | top | none |
organization | top | o |
organizationalUnit | top | ou |
person | top | sn, cn |
organizationalPerson | top | none |
The LDAP specification also lists some common attribute names. These attribute names tend to look confusing at first glance because many of them are only one or two characters long. You see these attributes in other places too, such as in X.509 certificates (for digital signatures and encryption). One of the reasons for the similarity is that LDAP uses many of the items defined in the X.500 series of recommendations (standards), which includes X.509.
Table 18.2 lists some of the common attributes and their meanings.
Table Some Common LDAP Attributes
Attribute Name | Meaning |
---|---|
objectClass | The classname of the object and its superclasses |
dc | A domain context-a part of a domain name |
cn | Common name, usually the name of the object |
sn | Surname-a person's family name (the last name in most Western cultures) |
c | The standard two-letter country code |
l | Locality (city, county, or other region) |
st | State or province |
o | Organization |
ou | Organizational unit |
title | A person's job title |
personalTitle | A person's personal (not job-related) title |
description | A description of the object |
A person's email address |
One other concept you should be aware of is that a context is really a set of names. You can create a context that is a subset of names by calling createSubcontext in the DirContext object. Essentially, a subcontext is just the set of names starting at a particular node in the directory tree.
The interesting thing is, you create a new node in the tree by creating a new subcontext. Listing 18.2 shows a program that adds two entries to the LDAP directory. Notice that the program must supply a username in the form of a SECURITY_PRINCIPAL and a password in the form of SECURITY_CREDENTIALS to make changes to the LDAP directory. Most servers let you read the directory anonymously but require a username and password to make changes.
Listing 18.2 Source Code for AddPerson.java
package usingj2ee.naming; import java.util.*; import javax.naming.*; import javax.naming.directory.*; public class AddPerson { public static void main(String[] args) { try { // Pass the security information to the directory context // The LDAP server requires a username (SECURITY_PRINCIPAL) // and password (SECURITY_CREDENTIALS) to add/remove // items. Hashtable props = new Hashtable(); props.put(Context.SECURITY_PRINCIPAL, "cn=Manager,dc=wutka,dc=com"); props.put(Context.SECURITY_CREDENTIALS, "secret"); // Get the initial context InitialDirContext ctx = new InitialDirContext(props); // Create a new set of attributes BasicAttributes attrs = new BasicAttributes(); // The item is an organizationalPerson, which is a subclass of person. // Person is a subclass of top. Store the class hierarchy in the // objectClass attribute Attribute classes = new BasicAttribute("objectclass"); classes.add("top"); classes.add("person"); classes.add("organizationalPerson"); // Add the objectClass attribute to the attribute set attrs.put(classes); // Store the other attributes in the attribute set attrs.put("sn", "Tippin"); attrs.put("title", "Computer Expert"); attrs.put("mail", "samantha@wutka.com"); // Add the new entry to the directory server ctx.createSubcontext("ldap://ldap.wutka.com/cn=Samantha Tippin,"+ "o=Wutka Consulting,dc=wutka,dc=com", attrs); // Create another set of attributes attrs = new BasicAttributes(); // Use the same objectClass attribute as before attrs.put(classes); // Set the other attributes attrs.put("sn", "Tippin"); attrs.put("title", "Computer Expert"); attrs.put("mail", "kaitlynn@wutka.com"); // Add another entry to the directory server ctx.createSubcontext("ldap://ldap.wutka.com/cn=Kaitlynn Tippin,"+ "o=Wutka Consulting,dc=wutka,dc=com", attrs); } catch (Exception exc) { exc.printStackTrace(); } } }
It's fairly easy to search through an LDAP directory using JNDI. You just call the search method in the DirContext. There are two main ways to search: by specifying either a set of attributes to match or an LDAP filter string.
Attribute matching is very straightforward, as you can see in Listing 18.3.
Listing 18.3 Source Code for Name Search.java
package usingj2ee.naming; import javax.naming.*; import javax.naming.directory.*; public class NameSearch { public static void main(String[] args) { try { // Get the initial context InitialDirContext ctx = new InitialDirContext(); // Create the search attributes - look for a surname of Tippin BasicAttributes searchAttrs = new BasicAttributes(); searchAttrs.put("sn", "Tippin"); // Search for items with the specified attribute starting // at the top of the search tree NamingEnumeration objs = ctx.search( "ldap://ldap.wutka.com/o=Wutka Consulting, dc=wutka, dc=com", searchAttrs); // Loop through the objects returned in the search while (objs.hasMoreElements()) { // Each item is a SearchResult object SearchResult match = (SearchResult)objs.nextElement(); // Print out the node name System.out.println("Found "+match.getName()+":"); // Get the node's attributes Attributes attrs = match.getAttributes(); NamingEnumeration e = attrs.getAll(); // Loop through the attributes while (e.hasMoreElements()) { // Get the next attribute Attribute attr = (Attribute) e.nextElement(); // Print out the attribute's value(s) System.out.print(attr.getID()+" = "); for (int i=0; i < attr.size(); i++) { if (i > 0) System.out.print(", "); System.out.print(attr.get(i)); } System.out.println(); } System.out.println("---------------------------------------"); } } catch (Exception exc) { exc.printStackTrace(); } } }
Searching by filter is a little more complicated. Any LDAP filter string must be surrounded by parentheses. To match all objects in the directory, you can use a filter string, such as (objectClass=*).
You can do comparisons using =, >=, <=, and ~= (approximately), like (age>=18).
The syntax for and, or, and not is a little strange. If you want to test age>=18 and sn=Smith, the expression is (&(age>=18)(sn=Smith)). Use & for and, | for or, and ! for not. For and and or, you can list as many expressions as you want to after the & or | characters. For not, you can only have a single expression.
For example, because you can only do a greater-than-or-equal-to comparison (>=), you do a greater-than by doing not-less-than-or-equal-to. For example, if age must be strictly greater than 18, use (!(age<=18)). If you need to combine the and and or operators, you must use parentheses to separate the expressions.
For example, you might want to search for age>=18 or (age >=13 and parentalPermission=true). The expression would be (|(age>=18)(&(age>=13)(parentalPermission=true))). The two expressions being ored together are (age>=18) and (&(age>=13)(parentalPermission=true)).
You can find a full definition of the LDAP search filter syntax in RFC1558 (http://www.ietf.org/rfc/rfc1558.txt).
Listing 18.4 shows a program that performs a simple filter search to dump out the entire contents of the directory.
Listing 18.4 Source Code for AllSearch.java
package usingj2ee.naming; import javax.naming.*; import javax.naming.directory.*; public class AllSearch { public static void main(String[] args) { try { // Get the initial context InitialDirContext ctx = new InitialDirContext(); SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); // Search for items with the specified attribute starting // at the top of the search tree NamingEnumeration objs = ctx.search( "ldap://ldap.wutka.com/o=Wutka Consulting, dc=wutka, dc=com", "(objectClass=*)", searchControls); // Loop through the objects returned in the search while (objs.hasMoreElements()) { // Each item is a SearchResult object SearchResult match = (SearchResult) objs.nextElement(); // Print out the node name System.out.println("Found "+match.getName()+":"); // Get the node's attributes Attributes attrs = match.getAttributes(); NamingEnumeration e = attrs.getAll(); // Loop through the attributes while (e.hasMoreElements()) { // Get the next attribute Attribute attr = (Attribute) e.nextElement(); // Print out the attribute's value(s) System.out.print(attr.getID()+" = "); for (int i=0; i < attr.size(); i++) { if (i > 0) System.out.print(", "); System.out.print(attr.get(i)); } System.out.println(); } System.out.println("---------------------------------------"); } } catch (Exception exc) { exc.printStackTrace(); } } }