An Introduction to LDAP
In the preceding
section, you have seen how to read a username and password from a database. In
this section, we look at LDAP (Lightweight Directory Access Protocol). LDAP
servers are more flexible and efficient for managing user information than are
database servers. Particularly in large organizations, in which data replication
is an issue, LDAP is preferred over relational databases for storing directory
information.
Because LDAP is less commonly used
than relational database technology, we briefly introduce it here. For an
in-depth discussion of LDAP, we recommend the "LDAP bible": Howes, Timothy et
al, 2003. Understanding and
Deploying LDAP Directory Services, (2nd ed.). Addison-Wesley
Professional.
LDAP Directories
LDAP uses a hierarchical database. It keeps
all data in a tree structure, not in a set of tables as a relational database
would. Each entry in the tree has:
Zero or more attributes. An attribute has a key and a value. An example attribute is
cn=John Q. Smith. The key cn stores the "common name." (See Table 10-3 for the
meaning of commonly used LDAP attributes.)
Table 10-3. Commonly Used LDAP Attributes
Attribute Name |
Meaning |
---|
dc |
Domain component |
cn |
Common name |
sn |
Surname |
dn |
Distinguished name |
o |
Organization |
ou |
Organizational unit |
uid |
Unique identifier |
One or more object classes. An object class defines the set of required and
optional attributes for this element. For example, the object class
person defines a required attribute cn and an optional
attribute telephoneNumber. Of course, the
object classes are different than Java classes, but they also support a notion
of inheritance. For example, inetOrgPerson is a subclass of
person with additional attributes.
A distinguished name (for
example, uid=troosevelt,ou=people,dc=corejsf,dc=com). The distinguished name is a sequence of attributes that
trace a path joining the entry with the root of the tree. There may be alternate
paths, but one of them must be specified as distinguished.
Figure 10-10
shows an example of a directory tree.
How to organize the directory tree
and what information to put in it can be a matter of intense debate. We do not
discuss the issues here. Instead, we assume that an organizational scheme has
been established and that the directory has been populated with the relevant
user data.
Configuring an LDAP Server
You have several options for running
an LDAP server to try out the programs in this section. Here are the most
popular choices:
The free OpenLDAP server (http://openldap.org), available for
Linux and Windows, and built into Mac OS X
A high-performance server such as the Sun Java System Directory
Server (http://www.sun.com/software/products/directory_srvr/home_directory.html),
which is available on a variety of platforms
Microsoft Active Directory
We give you brief instructions
for configuring OpenLDAP. If you use another directory server, the basic steps
are similar.
Our sample directory uses the standard object
class inetOrgPerson. (We use that class because it
has useful attributes such as uid and mail.) You should make sure that your LDAP server recognizes
this object class.
If you use OpenLDAP,
you need to edit the slapd.conf file before
starting the LDAP server. Locate the line that includes the
core.schema file, and add lines to include the
cosine.schema and inetorgperson.schema files. (On Linux, the default location for the
slapd.conf file is /etc/ldap, /etc/openldap, or
/usr/local/etc/openldap. The schema files are in
the schema subdirectory.)
Note
|
Alternatively, you can make adjustments to our sample data. For example, you can change inetOrgPerson to the more commonly available person, omit the uid and mail attributes, and use the sn attribute as the login name. If you follow that approach, you will need to change the attributes in the sample programs as well. |
In OpenLDAP, edit the suffix entry in
slapd.conf to match the sample data set. This
entry specifies the distinguished name suffix for this server. It should
read
suffix "dc=corejsf,dc=com"
You also need to configure an LDAP user
with administrative rights to edit the directory data. In OpenLDAP, add these
lines to slapd.conf:
rootdn "cn=Manager,dc=corejsf,dc=com"
rootpw secret
We recommend that you specify
authorization settings, although they are not strictly necessary for running the
examples in this sections. The following settings in slapd.conf permit
the Manager user to read and write passwords,
and everyone else to read all other attributes.
access to attr=userPassword
by dn.base="cn=Manager,dc=corejsf,dc=com" write
by self write
by * none
access to *
by dn.base="cn=Manager,dc=corejsf,dc=com" write
by self write
by * read
You can now start the LDAP server. On
Linux, run the slapd service (typically in the
/usr/sbin or /usr/local/libexec directory).
Next, populate the server with the
sample data. Most LDAP servers allow the import of LDIF (Lightweight Directory
Interchange Format) data. LDIF is a humanly readable format that lists all
directory entries, including their distinguished names, object classes, and
attributes. Listing
10-7 shows an LDIF file that describes our sample data:
For example, with
OpenLDAP, you use the ldapadd tool to add the data to the
directory:
ldapadd -f sample.ldif -x -D "cn=Manager,dc=corejsf,dc=com" -w secret
Before proceeding, it is a good idea
to double-check that the directory contains the data that you need. We suggest
that you use an LDAP browser such as JXPlorer (http://sourceforge.net/projects/jxplorer/) or Jarek Gawor's
LDAP Browser/Editor (http://www.mcs.anl.gov/~gawor/ldap/).
Both are convenient Java programs that let you browse the contents of any LDAP
server. Launch the program and configure it with the following options:
Host: localhost
Port: 389
Base DN: dc=corejsf,dc=com
User DN: cn=Manager,dc=corejsf,dc=com
Password: secret
Make sure that the LDAP server has
started, then connect. If everything is in order, you should see a directory
tree similar to the one shown in Figure 10-11.
Listing 10-7.
ldap/setup/sample.ldif
1. # Define top-level entry 2. dn: dc=corejsf,dc=com 3. objectClass: dcObject 4. objectClass: organization 5. dc: corejsf 6. o: A Sample Organization 7. 8. # Define an entry to contain people 9. # searches for users are based on this entry 10. dn: ou=people,dc=corejsf,dc=com 11. objectClass: organizationalUnit 12. ou: people 13. 14. # Define a user entry for Theodore Roosevelt 15. dn: uid=troosevelt,ou=people,dc=corejsf,dc=com 16. objectClass: inetOrgPerson 17. uid: troosevelt 18. sn: Roosevelt 19. cn: Theodore Roosevelt 20. mail: troosevelt@corejsf.com 21. userPassword: jabberwock 22. 23. # Define a user entry for Thomas Jefferson 24. dn: uid=tjefferson,ou=people,dc=corejsf,dc=com 25. objectClass: inetOrgPerson 26. uid: tjefferson 27. sn: Jefferson 28. cn: Thomas Jefferson 29. mail: tjefferson@corejsf.com 30. userPassword: mockturtle 31. 32. # Define an entry to contain LDAP groups 33. # searches for roles are based on this entry 34. dn: ou=groups,dc=corejsf,dc=com 35. objectClass: organizationalUnit 36. ou: groups 37. 38. # Define an entry for the "registereduser" role 39. dn: cn=registereduser,ou=groups,dc=corejsf,dc=com 40. objectClass: groupOfUniqueNames 41. cn: registereduser 42. uniqueMember: uid=tjefferson,ou=people,dc=corejsf,dc=com 43. 44. # Define an entry for the "invitedguest" role 45. dn: cn=invitedguest,ou=groups,dc=corejsf,dc=com 46. objectClass: groupOfUniqueNames 47. cn: invitedguest 48. uniqueMember: uid=troosevelt,ou=people,dc=corejsf,dc=com 49. uniqueMember: uid=tjefferson,ou=people,dc=corejsf,dc=com
|
Accessing LDAP Directory
Information
Once you have your LDAP database populated, it is time to
connect to it with a Java program. You use JNDI, an interface that unifies
various directory protocols.
Start by getting a directory context to the LDAP
directory, with the following incantation:
Hashtable env = new Hashtable();
env.put(Context.SECURITY_PRINCIPAL, userDN);
env.put(Context.SECURITY_CREDENTIALS, password);
DirContext initial = new InitialDirContext(env);
DirContext context = (DirContext) initial.lookup("ldap://localhost:389");
Here, we connect to the LDAP server at
the local host. The port number 389 is the default LDAP port.
If you connect to the LDAP database
with an invalid username/password combination, an
AuthenticationException is thrown.
Note
|
Sun's JNDI tutorial suggests an alternative way to connect to the server:
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:389"); env.put(Context.SECURITY_PRINCIPAL, userDN); env.put(Context.SECURITY_CREDENTIALS, password); DirContext context = new InitialDirContext(env);
However, it seems undesirable to hardwire the Sun LDAP provider into your code. JNDI has an elaborate mechanism for configuring providers, and you should not lightly bypass it. |
To list the attributes of a given
entry, specify its distinguished name and then use the getAttributes
method:
Attributes answer
= context.getAttributes("uid=troosevelt,ou=people,dc=corejsf,dc=com");
You can get a specific
attribute with the get method—for example:
Attribute commonNameAttribute = answer.get("cn");
To enumerate all attributes, you use the
NamingEnumeration class. The designers of this
class felt that they too could improve on the standard Java iteration protocol,
and they gave us this usage pattern:
NamingEnumeration attrEnum = answer.getAll();
while (attrEnum.hasMore()) {
Attribute attr = (Attribute) attrEnum.next();
String id = attr.getID();
...
}
Note the use of hasMore instead
of hasNext.
Since an attribute can have multiple
values, you need to use another NamingEnumeration to list them all:
NamingEnumeration valueEnum = attr.getAll();
while (valueEnum.hasMore()) {
Object value = valueEnum.next();
...
}
However, if you know that the attribute
has a single value, you can call the get
method to retrieve it:
String commonName = (String) commonNameAttribute.get();
You now know how to query the directory
for user data. Next, we take up operations for modifying the directory
contents.
To add a new entry, gather the set of attributes
in a BasicAttributes object. (The BasicAttributes class
implements the Attributes interface.)
Attributes attrs = new BasicAttributes();
attrs.put("objectClass", "inetOrgPerson");
attrs.put("uid", "alincoln");
attrs.put("sn", "Lincoln");
attrs.put("cn", "Abraham Lincoln");
attrs.put("mail", "alincoln@corejsf.com");
String pw = "redqueen";
attrs.put("userPassword", pw.getBytes());
Then call the createSubcontext
method. Provide the distinguished name of the new entry and the attribute
set.
context.createSubcontext("uid=alincoln,ou=people,dc=corejsf,dc=com", attrs);
Caution
|
When assembling the attributes, remember that the attributes are checked against the schema. Do not supply unknown attributes and be sure to supply all attributes that are required by the object class. For example, if you omit the sn of person, the createSubcontext method will fail. |
To remove an entry, call destroySubcontext:
context.destroySubcontext("uid=alincoln,ou=people,dc=corejsf,dc=com");
Finally, you may want to edit the
attributes of an existing entry. You call the method
context.modifyAttributes(distinguishedName, flag, attrs);
Here, flag is one of
DirContext.ADD_ATTRIBUTE
DirContext.REMOVE_ATTRIBUTE
DirContext.REPLACE_ATTRIBUTE
The attrs parameter contains a
set of the attributes to be added, removed, or replaced.
Conveniently, the BasicAttributes(String, Object) constructor constructs an attribute set with a single
attribute. For example,
context.modifyAttributes(
"uid=alincoln,ou=people,dc=corejsf,dc=com",
DirContext.ADD_ATTRIBUTE,
new BasicAttributes("telephonenumber", "+18005551212"));
context.modifyAttributes(
"uid=alincoln,ou=people,dc=corejsf,dc=com",
DirContext.REMOVE_ATTRIBUTE,
new BasicAttributes("mail", "alincoln@coresjf.com"));
context.modifyAttributes(
"uid=alincoln,ou=people,dc=corejsf,dc=com",
DirContext.REPLACE_ATTRIBUTE,
new BasicAttributes("userPassword", newpw.getBytes()));
Finally, when you are done with a
context, you should close it:
You now know enough about directory
operations to carry out the tasks that you will commonly need when working with
LDAP directories. A good source for more advanced information is the JNDI
tutorial at http://java.sun.com/products/jndi/tutorial.
However, we are not quite
ready to put together a JSF application that uses LDAP. It would be extremely
unprofessional to hardcode the directory URL and the manager password into a
program. Instead, these values should be specified in a configuration file.
The next section discusses various
options for the management of configuration parameters. We put the alternatives
to work with an application that allows users to self-register on a web site; we
use LDAP to store the user information.
javax.naming.directory.InitialDirContext Java SE
1.3
|
InitialDirContext(Hashtable env)
Constructs a directory context, using the given environment settings. The hash table can contain bindings for Context.SECURITY_PRINCIPAL, Context.SECURITY_CREDENTIALS, and other keys (see the API documentation for the javax.naming.Context interface for details).
|
javax.naming.Context Java SE 1.3
|
Object lookup(String name)
Looks up the object with the given name. The return value depends on the nature of this context. It commonly is a subtree context or a leaf object.
Context createSubcontext(String name)
Creates a subcontext with the given name. The subcontext becomes a child of this context. All path components of the name, except for the last one, must exist.
void destroySubcontext(String name)
Destroys the subcontext with the given name. All path components of the name, except for the last one, must exist.
void close()
Closes this context.
|
javax.naming.directory.DirContext Java SE 1.3
|
Attributes getAttributes(String name)
Gets the attributes of the entry with the given name.
void modifyAttributes(String name, int flag, Attributes modes)
Modifies the attributes of the entry with the given name. The value flag is one of DirContext.ADD_ATTRIBUTE, DirContext.REMOVE_ATTRIBUTE, or DirContext.REPLACE_ATTRIBUTE
|
javax.naming.directory.Attributes Java SE 1.3
|
Attribute get(String id)
Gets the attribute with the given ID.
NamingEnumeration getAll()
Yields an enumeration that iterates through all attributes in this attribute set.
void put(String id, Object value)
Adds an attribute to this attribute set.
|
javax.naming.directory.BasicAttributes Java SE
1.3
|
BasicAttributes(String id, Object value)
Constructs an attribute set that contains a single attribute with the given ID and value.
|
javax.naming.directory.Attribute Java SE 1.3
|
String getID()
Gets the ID of this attribute.
Object get()
Gets the first attribute value of this attribute if the values are ordered or an arbitrary value if they are unordered.
NamingEnumeration getAll()
Yields an enumeration that iterates through all values of this attribute.
|
javax.naming.NamingEnumeration Java SE 1.3
No comments:
Post a Comment