Recently I came into trying to authenticate users of an intranet web-application against the ActiveDirectory server that is used to authenticate them on their Windows desktop. Here is some code I used to achieve this.
I went into several steps, the first of them being creating a custom user registry to interface my web server and the AD server.
I was using Jetty as the web container so I had to develop an implementation of Jetty’s UserRealm but in any other web container or application things should be the same.
Mostly you need to do two things:
- Authenticate the user’s credentials
- Retrieve the user’s roles
1. Authenticating the user
1 2 3 4 5 6 7 |
Hashtable env = new Hashtable(); env.put(Context.PROVIDER_URL, "ldap://mydomain.com:389/"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.SECURITY_PRINCIPAL, "mydomain\\" + username); env.put(Context.SECURITY_CREDENTIALS, password); DirContext context = new InitialDirContext(env); |
Once you have created the initial context, the user has been authenticated by the AD server and everything is fine (creating the inital context will throw a NamingException otherwise).
However, since you are going to send the user’s credentials over the network you may want to have some confidence in the protocole that is used to negociate the connection. The javax.security.sasl.qop and other properties may be set to ensure that the protocole is safe.
This code adds the domain name to the username, that way the user doesn’t have to enter domain\username as its credentials but only its username.
You may want to force her to enter the domain or do some autodetection… as you like.
2. Retrieving the user’s roles
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Set memberOf = new HashSet(); SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchCtls.setCountLimit(1); searchCtls.setReturningAttributes(new String[] { "memberOf" }); String searchFilter = MessageFormat.format("(sAMAccountName={0})", new Object[] { username }); // search for objects NamingEnumeration answer = context.search("ou=Managed Objects," + "dc=mydomain,dc=com", searchFilter, searchCtls); // Loop through the search results if (answer.hasMoreElements()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); if (attrs != null) { Attribute memberOfAttr = attrs.get("memberOf"); if (memberOfAttr != null) { NamingEnumeration rolesEnum = memberOfAttr.getAll(); while (rolesEnum.hasMoreElements()) { Object role = rolesEnum.nextElement(); // save group names into group list memberOf.add(role.toString()); } } } } |
The roles that are returned are distinguished names, like cn=Joe Smith,ou=Sales,dc=mydomain,dc=com
so that may be another issue to map them to simpler names. Fortunately I didn’t need these roles for my application.
The second step for me was to actually enable single sign-on (authentication without asking for credentials).
I quickly discovered that the previous code was totally useless for that purpose. But I keep this one for a later post ;-)