LDAP Authentication using Java LDAP Authentication using Java java java

LDAP Authentication using Java


Following Code authenticates from LDAP using pure Java JNDI. The Principle is:-

  1. First Lookup the user using a admin or DN user.
  2. The user object needs to be passed to LDAP again with the user credential
  3. No Exception means - Authenticated Successfully. Else Authentication Failed.

Code Snippet

public static boolean authenticateJndi(String username, String password) throws Exception{    Properties props = new Properties();    props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");    props.put(Context.PROVIDER_URL, "ldap://LDAPSERVER:PORT");    props.put(Context.SECURITY_PRINCIPAL, "uid=adminuser,ou=special users,o=xx.com");//adminuser - User with special priviledge, dn user    props.put(Context.SECURITY_CREDENTIALS, "adminpassword");//dn user password    InitialDirContext context = new InitialDirContext(props);    SearchControls ctrls = new SearchControls();    ctrls.setReturningAttributes(new String[] { "givenName", "sn","memberOf" });    ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);    NamingEnumeration<javax.naming.directory.SearchResult> answers = context.search("o=xx.com", "(uid=" + username + ")", ctrls);    javax.naming.directory.SearchResult result = answers.nextElement();    String user = result.getNameInNamespace();    try {        props = new Properties();        props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");        props.put(Context.PROVIDER_URL, "ldap://LDAPSERVER:PORT");        props.put(Context.SECURITY_PRINCIPAL, user);        props.put(Context.SECURITY_CREDENTIALS, password);   context = new InitialDirContext(props);    } catch (Exception e) {        return false;    }    return true;}


This is my LDAP Java login test application supporting LDAP:// and LDAPS:// self-signed test certificate. Code is taken from few SO posts, simplified implementation and removed legacy sun.java.* imports.

Usage
I have run this in Windows7 and Linux machines against WinAD directory service. Application prints username and member groups.

$ java -cp classes test.LoginLDAP url=ldap://1.2.3.4:389 username=myname@company.fi password=mypwd

$ java -cp classes test.LoginLDAP url=ldaps://1.2.3.4:636 username=myname@company.fi password=mypwd

Test application supports temporary self-signed test certificates for ldaps:// protocol, this DummySSLFactory accepts any server cert so man-in-the-middle is possible. Real life installation should import server certificate to a local JKS keystore file and not using dummy factory.

Application uses enduser's username+password for initial context and ldap queries, it works for WinAD but don't know if can be used for all ldap server implementations. You could create context with internal username+pwd then run queries to see if given enduser is found.

LoginLDAP.java

package test;import java.util.*;import javax.naming.*;import javax.naming.directory.*;public class LoginLDAP {    public static void main(String[] args) throws Exception {        Map<String,String> params = createParams(args);        String url = params.get("url"); // ldap://1.2.3.4:389 or ldaps://1.2.3.4:636        String principalName = params.get("username"); // firstname.lastname@mydomain.com        String domainName = params.get("domain"); // mydomain.com or empty        if (domainName==null || "".equals(domainName)) {            int delim = principalName.indexOf('@');            domainName = principalName.substring(delim+1);        }        Properties props = new Properties();        props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");        props.put(Context.PROVIDER_URL, url);         props.put(Context.SECURITY_PRINCIPAL, principalName);         props.put(Context.SECURITY_CREDENTIALS, params.get("password")); // secretpwd        if (url.toUpperCase().startsWith("LDAPS://")) {            props.put(Context.SECURITY_PROTOCOL, "ssl");            props.put(Context.SECURITY_AUTHENTICATION, "simple");            props.put("java.naming.ldap.factory.socket", "test.DummySSLSocketFactory");                 }        InitialDirContext context = new InitialDirContext(props);        try {            SearchControls ctrls = new SearchControls();            ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);            NamingEnumeration<SearchResult> results = context.search(toDC(domainName),"(& (userPrincipalName="+principalName+")(objectClass=user))", ctrls);            if(!results.hasMore())                throw new AuthenticationException("Principal name not found");            SearchResult result = results.next();            System.out.println("distinguisedName: " + result.getNameInNamespace() ); // CN=Firstname Lastname,OU=Mycity,DC=mydomain,DC=com            Attribute memberOf = result.getAttributes().get("memberOf");            if(memberOf!=null) {                for(int idx=0; idx<memberOf.size(); idx++) {                    System.out.println("memberOf: " + memberOf.get(idx).toString() ); // CN=Mygroup,CN=Users,DC=mydomain,DC=com                    //Attribute att = context.getAttributes(memberOf.get(idx).toString(), new String[]{"CN"}).get("CN");                    //System.out.println( att.get().toString() ); //  CN part of groupname                }            }        } finally {            try { context.close(); } catch(Exception ex) { }        }           }    /**     * Create "DC=sub,DC=mydomain,DC=com" string     * @param domainName    sub.mydomain.com     * @return     */    private static String toDC(String domainName) {        StringBuilder buf = new StringBuilder();        for (String token : domainName.split("\\.")) {            if(token.length()==0) continue;            if(buf.length()>0)  buf.append(",");            buf.append("DC=").append(token);        }        return buf.toString();    }    private static Map<String,String> createParams(String[] args) {        Map<String,String> params = new HashMap<String,String>();          for(String str : args) {            int delim = str.indexOf('=');            if (delim>0) params.put(str.substring(0, delim).trim(), str.substring(delim+1).trim());            else if (delim==0) params.put("", str.substring(1).trim());            else params.put(str, null);        }        return params;    }}

And SSL helper class.

package test;import java.io.*;import java.net.*;import java.security.SecureRandom;import java.security.cert.X509Certificate;    import javax.net.*;import javax.net.ssl.*;public class DummySSLSocketFactory extends SSLSocketFactory {    private SSLSocketFactory socketFactory;    public DummySSLSocketFactory() {        try {          SSLContext ctx = SSLContext.getInstance("TLS");          ctx.init(null, new TrustManager[]{ new DummyTrustManager()}, new SecureRandom());          socketFactory = ctx.getSocketFactory();        } catch ( Exception ex ){ throw new IllegalArgumentException(ex); }    }      public static SocketFactory getDefault() { return new DummySSLSocketFactory(); }      @Override public String[] getDefaultCipherSuites() { return socketFactory.getDefaultCipherSuites(); }      @Override public String[] getSupportedCipherSuites() { return socketFactory.getSupportedCipherSuites(); }      @Override public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {        return socketFactory.createSocket(socket, string, i, bln);      }      @Override public Socket createSocket(String string, int i) throws IOException, UnknownHostException {        return socketFactory.createSocket(string, i);      }      @Override public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {        return socketFactory.createSocket(string, i, ia, i1);      }      @Override public Socket createSocket(InetAddress ia, int i) throws IOException {        return socketFactory.createSocket(ia, i);      }      @Override public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {        return socketFactory.createSocket(ia, i, ia1, i1);      }}class DummyTrustManager implements X509TrustManager {    @Override public void checkClientTrusted(X509Certificate[] xcs, String str) {        // do nothing    }    @Override public void checkServerTrusted(X509Certificate[] xcs, String str) {        /*System.out.println("checkServerTrusted for authType: " + str); // RSA        for(int idx=0; idx<xcs.length; idx++) {            X509Certificate cert = xcs[idx];            System.out.println("X500Principal: " + cert.getSubjectX500Principal().getName());        }*/    }    @Override public X509Certificate[] getAcceptedIssuers() {        return new java.security.cert.X509Certificate[0];    }}


You will have to provide the entire user dn in SECURITY_PRINCIPAL

like this

env.put(Context.SECURITY_PRINCIPAL, "cn=username,ou=testOu,o=test");