Java: Sample Active Directory authentication code
January 3, 2014 19 Comments
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(); } }
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
Thanks as always. Made the ldap index variable as thread-safe.
Thank you for your blog.
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.
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
Thanks…. Its working fine
Thank you for this blog….
That is very helpful to me …
Thank you very much!!!
Thanks a lot venkat.. great piece of code.. serves my needs perfectly..
Thanks for the code. It’s working. nice work….
Thanks nice work!
Awesome!!!
Thanks a lot!!
Excelent !! well done bien jugado
Excellent!! Thanks a lot
how to get user details AD in java
Hi Venkat,
can you please help me to find how can I authenticate a user in Swings based desktop application using Windows credentials.
I tried for JCIF,but, it is not working,
thank you so much, it’s working well
Pingback: Java Ad Login - LoginCrunch