So maybe like me, you're a busy java webapp developer (isn't everybody these days?) who just got told/decided for yourself that instead of creating yet another app for which users need to remember a username/password pair, that you will instead do the Right Thing and connect up to your company/organisations central LDAP (or ActiveDirectory) .
And (again like me) you might even need to have cause to setup your own LDAP server either for testing or some other reasons you can't use the existing AD setup in your org.
So instead of replicating the tedious hours of frustration that I've spent - let me give you a quick run down of the pertinent facts you need to get up and running.
First the obligatory intro to LDAP:its basically a tree of objects. Each object belongs to 1 or more "classes". Classes are just sets of compulsory and optional attributes. Attributes a just name-value pairs (think java properties files).
So getting a bit more specific, if you have a person class that has the compulsory attributes "firstname" and "lastname" and then another class called orgPerson that has a compulsory attribute "email" then you can have an object that has "belongs" to both classes and so will always have a firstname, lastname and email.
eg.
firstname: homer
lastname: simpson
email: hsimpson@springfield.com
Of course the above classes (and attributes) are made up, but are pretty close to the real thing, as in LDAP you can have any class you like, all you need to do is define a schema for it (just like with relational DBs really). Handily, most LDAP server implementations seem to come with a whole bunch of predefined classes (not sure if they are actually a standard or just convention) for users, groups, etc.
Now the whole tree bit is just as easy, its basically just made up of objects (usually belonging to specific 'container' style classes) forming, well, a tree. Now 'container' style classes are ones such as "o" (organisation), "ou" (organizational unit), etc. There is also "dc" which is normally used when creating a tree based on DNS names (eg. www.springfield.com)
Now the only slightly wierd thing about LDAP is the way the naming syntax works, its all about commas. So to say the homer belongs to the staff group at the springfield site of the bruns power company you would write:
uid=homer, ou=users, ou=springfield, o=burnspower
this is assuming that the homer object has am attribute called "uid" that identifies it (its "Relative Distinguished Name" in LDAP speak) and that you've already defined the "users", "springfield" and "burnspower" org units and org respectively. And how would you do that, well you could use a
LDAP GUI tool to connect to your LDAP server or write ldif files (which are basically serialised text versions of parts of the LDAP directory tree) to define them,
eg.
dn: o=burnspower
objectClass: top
objectClass: organization
o: burnspower
dn: ou=springfield,o=burnspower
objectClass: top
objectClass: organizationalUnit
ou: springfield
dn: ou=users, ou=springfield,o=burnspower
objectClass: top
objectClass: organizationalUnit
ou: users
As you can see, you just define each object one after the other, and with the "dn" (Distinguished Name") you set the "path" of the object in the tree, just like you would if you were creating a series of nested folders on your PCs filesystem.
Now this brings us nicely to doing some real work, like actually setting up the LDAP server. Basically while there are a few different commercial and open source LDAP servers out there, basically ignore all the rest and use OpenLDAP. Even though it might me slow and chunky, it is the lest painful way to go if you've just got a linux box and not much time or patience.
Often OpenLDAP will be part of the default OS install or just a few clicks/commands away using your package manager of choice.
Once you do have it installed and configured (for OpenLDAP usually just means setting a couple of options in the
/etc/openldap/slapd.conf), use something like the above ldif files to set up your basic directory structure and then you can just go start plonking your users in there (again using wither ldif files or via GUI) . The main thing you need to decide is what class(es) you want to use for your user objects - for myself I've found inetOrgPerson had all the attributes I needed.
eg.
dn: uid=homer ou=users, ou=springfield, o=burnspower
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Homer Simpson
displayName: HomerSimpson
givenName: Homer
mail: homer.simpson@burnspower.com
mobile: 122 333 4567
sn: Simpson
uid: homer
userPassword:: adlr0912384nq4o358ndfadasl
Note1: the user has both the inetOrgPerson and organizationalPerson classes in a kinda inheritance way to pickup all the attributes we want to use.
Note2: the above userPassword attribute is how my gui ldap browser exported the ldif file, but I suspect that the correct way to specify them (for OpenLDAP) at least is:
{CRYPT}AF387HED3SDA
Note3: all the above password binaries are just random typing on my part and so dont match :-)
Setting up groups is also pretty straight forward, the 2 handy classes you want are groupOfNames and groupOfUniqueNames. Note that both have attributes which let you specify the members of the group by having multiple instances of that attribute,
eg.
dn: cn= Managers,ou=groups,ou=springfield,o=burnspower
objectClass: groupOfUniqueNames
objectClass: top
cn: Managers
description: the bosses
uniqueMember: uid=monty, ou=users, ou=springfield, o=burnspower
uniqueMember: uid=smithers, ou=users, ou=springfield, o=burnspower
And that about covers it from the LDAP side of things.
Now we can actually get to the java side and setup a single authentication service.
Basically LDAP has lots of different ways to do authentication, most of which seem very complicated and are definitely overkill my mine and most small installs, so I'll just stick to the simple ones:
Method 1.First of you need to know that in LDAP-speak logging into to an LDAP server is called 'binding'. When you "bind" to the server, you can do it as a particular user as long as you specify the users "DN" as the login.
You also give the password and the LDAP server goes looking for the password attribute for the object given you specified in the DN (eg. uid=homer ou=users, ou=springfield, o=burnspower) and if the bind is successful you know the user is authenticated and hooray, you're done! Once you have bound to the server, you can then also grab all the users details (email, lastname, whatever) using the supplied DN to lookup the user object for yourself.
Method 2.Another way to do it is to use an "admin" users login to bind to the LDAP server, fetch the user object for the given DN and then compare the password attribute in your app code. You might want to do this for instance if you need to check which groups a user belongs to but the user themselves dont have permission to access the groups part of the directory tree (I forget to mention that you can set permissions for users to read/write (or not) any part of the tree - again just like you do with a filesystem). So you bind to the LDAP server as an 'admin' user which does have permission to go looking up the groups (or whatever) part of the directory tree.
Security Side Note:One thing should point at this stage is that while the LDAP protocol itself is binary (bit wierd to find that out in this age of internet text protocols - at least its defined in some RFCs) its still all sent in the clear, so the usual modius operandi is to do all the LDAP connections over SSL so that people don't go snooping on your passwords in the net traffic.
Anyway, now that we've covered the connecting to server and authenticating theory, the actual java code (for method 1) is a walk in the park:
public boolean authenticate(String username, String password) {
String userSuffix = ",ou=users,ou=springfield ,o=burnspower";
String userRDNPrefix = "uid=";
String hostname = "ldap.burnspower.com";
String port = "389";
String userRDN = userRDNPrefix+username;
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://"+hostname+":"+port+"/");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userRDN+userSuffix);
env.put(Context.SECURITY_CREDENTIALS, password);
// Create the initial context
try {
DirContext ctx = new InitialDirContext(env);
} catch (AuthenticationException ae) {
System.out.println("Invalid Login");
return false;
} catch (NamingException e) {
e.printStackTrace();
return false;
}
return true;
}
And voila! one less set of passswords for your users to remember.
Labels: java