Storing user information in a relational database along with your other application data is a great strategy when your application controls the user data. However, user data often needs to be shared between applications or managed by external systems. In these cases, an LDAP directory server is frequently called upon to be the central repository for user information. If you are faced with a central LDAP user repository, you'll be relieved to know that the LDAP login module is just as easy to use as the ones we've seen so far. Note: LDAP makes sense only if you need to share information between systems. Don't introduce LDAP just to manage information for one application. How do I do that?LDAP has some truly wonderful features. It has a standard network protocol and a standard query mechanism, which guarantees that any client can talk to any server without the need for vendor-proprietary drivers or customized queries. LDAP also has standardized schemas, which allow for standard data representations between servers. It might seem like LDAP is the Holy Grail of standardized user management. Unfortunately, it doesn't quite work out that way. With all of the flexibility LDAP offers, nearly every LDAP system has its own twist on representing users. As a result, LDAP login modules always require a significant amount of configuration to make them work. And, despite the differences, there's enough in common that a simple example aptly illustrates how to map your user data into information that JBoss can use to authenticate and authorize users. Here is an LDAP data interchange format (LDIF) file that contains the data we'll be working from in this lab: dn: o=jbossnotebook objectclass: top objectclass: organization o: jbossnotebook dn: ou=People,o=jbossnotebook objectclass: top objectclass: organizationalUnit ou: People dn: uid=norman,ou=People,o=jbossnotebook objectclass: top objectclass: uidObject objectclass: person uid: norman cn: Norman sn: Richards userPassword: secretpassword1 dn: uid=sam,ou=People,o=jbossnotebook objectclass: top objectclass: uidObject objectclass: person uid: sam cn: Sam sn: Griffith userPassword: secretpassword2 dn: ou=Roles,o=jbossnotebook objectclass: top objectclass: organizationalUnit ou: Roles dn: cn=User,ou=Roles,o=jbossnotebook objectclass: top objectclass: groupOfNames cn: User member: uid=norman,ou=People,o=jbossnotebook member: uid=sam,ou=People,o=jbossnotebook Note: Make sure that your LDAP server is indexing the objectclass and member attributes. You'll need to search on them. This data provides two primary entries, one for a user and one for a group, along with several structural entities to help keep the data organized. The user is a simple person object. We'll use the uid and userPassword attributes to indicate the user's name and password, respectively. The group is a groupOfNames object whose member attributes indicate who the group members are. Let's think through how we would authenticate and authorize a user from this data. Suppose the user sam enters his password, secretpassword. First, our login module would need to locate the entry for sam. This would require knowing that we should use the uid=sam enTRy under ou=People,dc=jboss,dc=org for the uid=sam enTRy. We can let the LDAP server take care of validating the user's credentials. Note: Although LDAP has standardized queries, there is no declarative way to express complex compound queries like you can with SQL. To find the roles for the user, we'd need to know to look for groups under ou=Roles,dc=jboss,dc=org. We'd also need to know to look for entries that contain member attributes whose value is the DN of the user's entry. Once we have those group names, we'd need to know how to map the DN of the role entry into a role name useful to JBoss. Although your schema may be quite different, you'll need to follow the same thought process. If you can do that, you'll have no problem making your data work. Now let's look at the configuration necessary to authenticate against an LDAP server that contains the data shown earlier. We'll need to use LdapLoginModule: <application-policy name="todo"> <authentication> <login-module code="org.jboss.security.auth.spi.LdapLoginModule" flag="required"> <module-option name="java.naming.factory.initial"> com.sun.jndi.ldap.LdapCtxFactory </module-option> <module-option name="java.naming.provider.url"> ldap://localhost/ </module-option> <module-option name="java.naming.security.authentication"> simple </module-option> <module-option name="principalDNPrefix">uid=</module-option> <module-option name="principalDNSuffix"> ,ou=People,o=jbossnotebook </module-option> <module-option name="rolesCtxDN"> ou=Roles,o=jbossnotebook </module-option> <module-option name="uidAttributeID">member</module-option> <module-option name="matchOnUserDN">true</module-option> <module-option name="roleAttributeID">cn</module-option> <module-option name="roleAttributeIsDN">false</module-option> </login-module> </authentication> </application-policy> There are a lot of options to configure, but the process isn't that tricky. The java.naming.* options are the JNDI properties to use to connect to the LDAP server. Notice that we are using simple authentication without specifying the DN and password of the user we want to connect as. The LDAP login module will attempt to connect as the user in question. It will construct a DN by prepending principalDNPrefix to the username and then appending the principalDNSuffix value. rolesCtxDN provides the structural entity that holds the role entries. The uidAttributeID and matchOnUserDN values specify that we are searching for entries whose member attributes match the DN of the user we are trying to authenticate. Finally, the roleAttributeID and roleAttributeIsDN values instruct the login module to use the cn attribute value of the role entry as the name of the role to assign to the user in JBoss. For the role object in our sample data, the cn attribute is JBossAdmin. If you add the login module definition to your login-config.xml file, the application will start to authenticate against the user data you have in your LDAP server. Note: Don't forget to restart after modifying login-config.xml. What about......more complex LDAP structures? This is a very simplistic approach and it doesn't cover the many cases in which the DNs of your user entries don't fit such a neat and tidy pattern. Many LDAP servers use compound RDNs, or RDNs that don't make useful usernames. It's also common to have user information spread out over an entire subtree instead of neatly, one level below some root level. The LDAP login module doesn't address these complex cases. You would need to extend the login module to provide that functionality. |