Active Directory Authentication in Java

Doing Active Directory authentication in Java can be a nightmare. Documentation is scarse and Java APIs are usually counter-intuitive.

I’ve spent a good time to get it right (or what I guess is right), and I hope that this code sample can help others. Just use it, code is under WTFPL license.

Summary to validate if a user is in a group:

  1. Java requires the user full DN to authenticate;
  2. Authenticate to LDAP using a master user;
  3. Search user DN;
  4. Reauthenticate using user DN;
  5. Fetch all groups of user;
  6. Check if the required group is in it.
package com.tnegri.sample;

import java.util.*;

import javax.naming.*;
import javax.naming.directory.*;

public final class ActiveDirectoryFacade {

    private static final MASTER_USER_DN;
    private static final MASTER_PASSWORD;

    static {
        // Sample configuration.
        MASTER_USER_DN = "CN=masteruser,OU=Users,DC=tnegri,DC=com";
        MASTER_PASSWORD = "m4sterP4ssw0rd";
    }

    private final String ldapUrl;
    private final String searchBase;

    /**
     * @param ldapUrl LDAP URL, e.g. "ldap://10.0.0.1:389"
     * @param searchBase Base for doing user search, 
     *                   e.g. "OU=Users,DC=tnegri,DC=com"
     */
    public ActiveDirectoryFacade(String ldapUrl, String searchBase) {
        this.ldapUrl = ldapUrl;
        this.searchBase = searchBase;
    }

    public boolean hasGroup(String username, String password, 
                            String groupObjectName) 
            throws NamingException {
        List<String> allGroups = getAllGroups(username, password);
        return allGroups.contains(groupObjectName);
    }

    private List<String> getAllGroups(String username, String password)
            throws NamingException {
        List<String> result = new ArrayList<>();

        String attributeToLookup = "memberOf";

        /*
         * 1. Authenticate using master user.
         */
        DirContext ctx = authenticate();

        /*
         * 2. Searches by "sAMAccountName" to recover the full
         *    DN of the username trying to login.
         */
        SearchControls searchControls = new SearchControls();
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        searchControls.setReturningAttributes(
                new String[] {"distinguishedName"});
        NamingEnumeration<SearchResult> searchResults = ctx.search(
                searchBase,
                String.format("(sAMAccountName=%s)", username),
                searchControls);
        if (!searchResults.hasMore()) {
            /*
             * If can't resolve DN, the user doesn't exists.
             */
            throw new NamingException();
        }
        SearchResult searchResult = searchResults.next();
        Attributes attributes = searchResult.getAttributes();
        Attribute attribute = attributes.get("distinguishedName");
        String userObject = (String) attribute.get();

        /*
         * 3. Authenticates to LDAP with the user, will throw if
         *    password is wrong.
         */
        ctx.close();
        ctx = authenticate(userObject, password);

        /*
         * 4. Fetch all groups of user.
         */
        attributes = ctx.getAttributes(userObject, 
                                       new String[] {attributeToLookup});

        NamingEnumeration<? extends Attribute> allAttributes =
                attributes.getAll();
        while (allAttributes.hasMoreElements()) {
            attribute = allAttributes.nextElement();
            int size = attribute.size();
            for (int i = 0; i < size; i++) {
                String attributeValue = (String) attribute.get(i);
                result.add(attributeValue);
            }
        }

        ctx.close();

        return result;
    }

    private DirContext authenticate() throws NamingException {
        return authenticate(null, null);
    }

    private DirContext authenticate(String username, String password)
            throws NamingException {
        String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
        String securityAuthentication = "simple";

        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
        env.put(Context.SECURITY_AUTHENTICATION, securityAuthentication);
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_PRINCIPAL,
                username != null ? username : MASTER_USER_DN);
        env.put(Context.SECURITY_CREDENTIALS,
                password != null ? password : MASTER_PASSWORD);

        DirContext ctx = new InitialDirContext(env);

        return ctx;
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s