Java: Sample Active Directory authentication code

Here is a sample Java code to authenticate against Windows Active Directory server.

  • The code finds all available active directory servers in your network.
  • It uses one of the available active directory server for authentication.
  • If an active directory server is down then it starts using next available server if any.
  • This class is thread-safe, you can create one instance and re-use them multiple times.
  • I tested this code from Linux and Windows box.

You can find the below source code in GitHub as well.
ActiveDirectoryAuthentication.java

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;

public class ActiveDirectoryAuthentication {

	private static final String CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory";

	private String ldapServerUrls[];

	private int lastLdapUrlIndex;

	private final String domainName;

	public ActiveDirectoryAuthentication(String domainName) {
		this.domainName = domainName.toUpperCase();

		try {
			ldapServerUrls = nsLookup(domainName);
		} catch (Exception e) {
			e.printStackTrace();
		}
		lastLdapUrlIndex = 0;
	}

	public boolean authenticate(String username, String password) throws LoginException {
		if (ldapServerUrls == null || ldapServerUrls.length == 0) {
			throw new AccountException("Unable to find ldap servers");
		}
		if (username == null || password == null || username.trim().length() == 0 || password.trim().length() == 0) {
			throw new FailedLoginException("Username or password is empty");
		}
		int retryCount = 0;
		int currentLdapUrlIndex = lastLdapUrlIndex;
		do {
			retryCount++;
			try {
				Hashtable<Object, Object> env = new Hashtable<Object, Object>();
				env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY_CLASS);
				env.put(Context.PROVIDER_URL, ldapServerUrls[currentLdapUrlIndex]);
				env.put(Context.SECURITY_PRINCIPAL, username + "@" + domainName);
				env.put(Context.SECURITY_CREDENTIALS, password);
				DirContext ctx = new InitialDirContext(env);
				ctx.close();
				lastLdapUrlIndex = currentLdapUrlIndex;
				return true;
			} catch (CommunicationException exp) {
				exp.printStackTrace(); // TODO you can replace with log4j or slf4j API
				// if the exception of type communication we can assume the AD is not reachable hence retry can be attempted with next available AD
				if (retryCount < ldapServerUrls.length) {
					currentLdapUrlIndex++;
					if (currentLdapUrlIndex == ldapServerUrls.length) {
						currentLdapUrlIndex = 0;
					}
					continue;
				}
				return false;
			} catch (Throwable throwable) {
				throwable.printStackTrace();
				return false;
			}
		} while (true);
	}

	private static String[] nsLookup(String argDomain) throws Exception {
		try {
			Hashtable<Object, Object> env = new Hashtable<Object, Object>();
			env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
			env.put("java.naming.provider.url", "dns:");
			DirContext ctx = new InitialDirContext(env);
			Attributes attributes = ctx.getAttributes(String.format("_ldap._tcp.%s", argDomain), new String[] { "srv" });
			// try thrice to get the KDC servers before throwing error
			for (int i = 0; i < 3; i++) {
				Attribute a = attributes.get("srv");
				if (a != null) {
					List<String> domainServers = new ArrayList<String>();
					NamingEnumeration<?> enumeration = a.getAll();
					while (enumeration.hasMoreElements()) {
						String srvAttr = (String) enumeration.next();
						// the value are in space separated 0) priority 1)
						// weight 2) port 3) server
						String values[] = srvAttr.toString().split(" ");
						domainServers.add(String.format("ldap://%s:%s", values[3], values[2]));
					}
					String domainServersArray[] = new String[domainServers.size()];
					domainServers.toArray(domainServersArray);
					return domainServersArray;
				}
			}
			throw new Exception("Unable to find srv attribute for the domain " + argDomain);
		} catch (NamingException exp) {
			throw new Exception("Error while performing nslookup. Root Cause: " + exp.getMessage(), exp);
		}
	}
}

Here is sample code to test this class.

	public static void main(String[] args) {
		ActiveDirectoryAuthentication authentication = new ActiveDirectoryAuthentication("YOUR.DOMAIN.COM");
		try {
			boolean authResult = authentication.authenticate("Username", "password");
			System.out.print("Auth: " + authResult);
		} catch (LoginException e) {
			e.printStackTrace();
		}
	}
Advertisements

12 Responses to Java: Sample Active Directory authentication code

  1. mohanmca says:

    Thanks quite useful.

    But it is not thread safe. You use instance level variable inside method.

    1) Remove currentLdapUrlIndex from class level
    2) move int currentLdapUrlIndex = 0; into authenticate method
    3) Decorate other variable as final

  2. Venkat says:

    Thanks as always. Made the ldap index variable as thread-safe.

  3. jhlee says:

    Thank you for your blog.

  4. Anonymous says:

    After having such a hard time trying to make Spring LDAP to run, I found your page. Exactly what I need and to the point.

  5. Dharmendra says:

    I got a requirement to implement the single sign on web application. I have downloaded your code and after ran on server I got a error. The below is log error message.

    Oracle Corporation
    Java config name: null
    Native config name: C:\Windows\krb5.ini
    Loaded from native config
    >>> KdcAccessibility: reset
    default etypes for default_tkt_enctypes: 23 3 1 16.
    >>> KrbAsReq creating message
    >>> KrbKdcReq send: kdc=localhost UDP:88, timeout=30000, number of retries =3, #bytes=147
    >>> KDCCommunication: kdc=localhost UDP:88, timeout=30000,Attempt =1, #bytes=147
    SocketTimeOutException with attempt: 1
    >>> KDCCommunication: kdc=localhost UDP:88, timeout=30000,Attempt =2, #bytes=147

    It’s repeatedly throwing the error SocketTimeOutException . Could you please let me do I need to configure any thing.

    Appreciate for your support !!!

    Dharmendra

  6. Anonymous says:

    Thanks…. Its working fine

  7. Paddy says:

    Thank you for this blog….
    That is very helpful to me …

  8. Anonymous says:

    Thank you very much!!!

  9. Ravi Akella says:

    Thanks a lot venkat.. great piece of code.. serves my needs perfectly..

  10. Anonymous says:

    Thanks for the code. It’s working. nice work….

  11. Anonymous says:

    Thanks nice work!

  12. Anonymous says:

    Awesome!!!

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

%d bloggers like this: