Tuesday, October 20, 2009

An Introduction to LDAP


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 NameMeaning
    dcDomain component
    cnCommon name
    snSurname
    dnDistinguished name
    oOrganization
    ouOrganizational unit
    uidUnique
    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.





Figure 10-10. 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:


  . ldap/misc/sample.ldif


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.





Figure 10-11. Inspecting an LDAP directory tree







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:


  context.close();


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










  • boolean hasMore()


    Returns true if this
    enumeration object has more elements.



  • Object next()


    Returns the next element of this
    enumeration.

No comments: