Search code examples
javaauthenticationldapjndi

LDAP Error 49 from Java JNDI Connection


* CHECK BELOW AN ANSWER WITH A FULL WORKING SOLUTION *

I'm stucked days with a Java LDAP connection problem.

This is my method to connect to a LDAP Server:

public boolean authenticate(String user, String password) {
    StringBuilder url = new StringBuilder("ldap://");
    url.append("10.0.0.1");
    url.append(":");
    url.append(389);

    StringBuilder securityPrincipal = new StringBuilder("uid=");
    securityPrincipal.append(user);
    securityPrincipal.append(",");
    securityPrincipal.append("dc=XXXXX,dc=YYY,dc=ZZ");

    Hashtable<String, String> env;
    env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, url.toString());
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, securityPrincipal.toString());

    env.put(Context.SECURITY_CREDENTIALS, password);

    System.out.println(url);
    System.out.println(securityPrincipal.toString());

    try {
        ldap = new InitialLdapContext(env, null);
    } catch (NamingException e) {
        e.printStackTrace();
        return false;
    }

    return true;
}

For security and disclosure reasons, I ommited "dc" for XXXXX, YYY and ZZ and changed LDAP server's IP.

I used the same combination into a PHP software (GLPI) and it worked like a charm. But, for GOD's sake, Java cannot accept this LDAP configurarion giving me always this error:

javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
    at com.sun.jndi.ldap.LdapCtx.mapErrorCode(Unknown Source)
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source)
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source)

Full dn is this:

uid=tiagoadami,dc=XXXXX,dc=YYY,dc=ZZ

Variable user is filled with "tiagoadami" and variable "argument" is filled with the plain text password.

This is very annoying. My password is correct, and I'm authenticating into every single application with username "tiagoadami" and the password. I'm out of options right now. Can anyone help me?


Solution

  • I'm glad for the help of everyone who commented. Without your help I would be stucked yet. I figured out that the LDAP server does not allow bind with just the base DN.

    After a desperate trying, I could connect using the full path of the tree:

    uid=tiagoadami,ou=proto,ou=serv,ou=user,ou=collab,ou=all,dc=XXXXX,dc=YYY,dc=ZZ
    

    instead of:

    uid=tiagoadami,dc=XXXXX,dc=YYY,dc=ZZ
    

    Sooooooooo looooooooong... with these 2 classes I was able to solve ALL my LDAP problems. I changed them to search inside the tree and get the full DN for a given UID. Here they are for anyone with the same problems:

    TrustAllCertificatesSSLSocketFactory.java

    package com.adamiworks.commonutils.ldap;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.security.SecureRandom;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    /**
     * This class accept all SSL Certificates even if it can assure its
     * Certification Institute.
     * 
     * DO NOT USE AT PRODUCTION ENVIRONMENTS
     * 
     * @author Tiago J. Adami
     *
     */
    public class TrustAllCertificatesSSLSocketFactory extends SocketFactory {
        private SocketFactory socketFactory;
    
        public TrustAllCertificatesSSLSocketFactory() {
            try {
                SSLContext ctx = SSLContext.getInstance("SSL");
                ctx.init(null, new TrustManager[] { new AllCertificatesTrustManager() }, new SecureRandom());
                socketFactory = ctx.getSocketFactory();
            } catch (Exception ex) {
                ex.printStackTrace(System.err); /* handle exception */
            }
        }
    
        public static SocketFactory getDefault() {
            return new TrustAllCertificatesSSLSocketFactory();
        }
    
        @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);
        }
    
        private class AllCertificatesTrustManager implements X509TrustManager {
            public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                // do nothing
            }
    
            public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                // do nothing
            }
    
            public X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[0];
            }
        }
    }
    

    LdapUtils.java

    package com.adamiworks.commonutils.ldap;
    
    import java.util.Hashtable;
    import java.util.Properties;
    
    import javax.naming.AuthenticationException;
    import javax.naming.Context;
    import javax.naming.NamingEnumeration;
    import javax.naming.NamingException;
    import javax.naming.directory.Attributes;
    import javax.naming.directory.DirContext;
    import javax.naming.directory.InitialDirContext;
    import javax.naming.directory.SearchControls;
    import javax.naming.directory.SearchResult;
    
    /**
     * Authenticates with LDAP Servers. Just using a single UID this class goes deep
     * inside the user's tree and find the full DN for the given UID. It also allows
     * to connect to servers when you don't have the certificate yet... but use this
     * feature at your own risk!
     * 
     * @author Tiago J. Adami
     *
     */
    public class LdapUtils {
        private InitialDirContext ldap;
        private String host;
        private int port;
        private boolean useSSL;
        private boolean ignoreCertificates;
        private String basedn;
    
        public InitialDirContext getLdap() {
            return ldap;
        }
    
        public boolean isIgnoreCertificates() {
            return ignoreCertificates;
        }
    
        public void setIgnoreCertificates(boolean ignoreCertificates) {
            this.ignoreCertificates = ignoreCertificates;
        }
    
        public String getHost() {
            return host;
        }
    
        public int getPort() {
            return port;
        }
    
        public String getBasedn() {
            return basedn;
        }
    
        public boolean isUseSSL() {
            return useSSL;
        }
    
        public void setUseSSL(boolean useSSL) {
            this.useSSL = useSSL;
        }
    
        /**
         * Default constructor
         * 
         * @param host
         * @param port
         * @param basedn
         * @param useSSL
         * @param ignoreCertificates
         */
        public LdapUtils(String host, int port, String basedn, boolean useSSL, boolean ignoreCertificates) {
            super();
            this.host = host;
            this.port = port;
            this.useSSL = useSSL;
            this.basedn = basedn;
            this.ignoreCertificates = ignoreCertificates;
        }
    
        /**
         * Authenticates an user and password from LDAP credentials;
         * 
         * @param uid
         * @param password
         * @return
         * @throws NamingException
         */
        public boolean authenticate(String uid, String password) {
            try {
                String url = getUrl();
                String dn = this.getDnByUid(uid);
    
                Properties env = new Properties();
                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                env.put(Context.PROVIDER_URL, url);
    
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
    
                env.put(Context.SECURITY_PRINCIPAL, dn);
                env.put(Context.SECURITY_CREDENTIALS, password);
    
                if (this.useSSL) {
                    env.put(Context.SECURITY_PROTOCOL, "ssl");
                }
    
                if (this.useSSL && this.ignoreCertificates) {
                    env.put("java.naming.ldap.factory.socket", "com.adamiworks.commonutils.ldap.TrustAllCertificatesSSLSocketFactory");
                }
    
                ldap = new InitialDirContext(env);
            } catch (AuthenticationException e) {
                e.printStackTrace();
                return false;
            } catch (NamingException e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /**
         * Returns the url based on SSL or not
         * 
         * @return
         */
        private String getUrl() {
            StringBuilder url = new StringBuilder();
    
            url.append(this.useSSL ? "ldaps://" : "ldap://");
            url.append(host);
            url.append(":");
            url.append(port);
            return url.toString();
        }
    
        /**
         * Returns the url based on SSL or not
         * 
         * @return
         */
        private String getUrlWithoutSsl() {
            StringBuilder url = new StringBuilder();
            url.append("ldap://");
            url.append(host);
            return url.toString();
        }
    
        /**
         * Return LDAP authentication modes allowed by the server
         * 
         * @param url
         * @return
         * @throws NamingException
         */
        public Attributes getLdapAuths() throws NamingException {
    
            // Create initial context
            DirContext ctx = new InitialDirContext();
    
            // Read supportedSASLMechanisms from root DSE
            Attributes attrs = ctx.getAttributes(this.getUrl(), new String[] { "supportedSASLMechanisms" });
    
            System.out.println(attrs);
    
            return attrs;
    
        }
    
        /**
         * Returns the full DN (distinct name) for a given UID
         * 
         * @param uid
         *            the UID name of the user
         * @return full tree path of LDAP
         * @throws NamingException
         */
        @SuppressWarnings("rawtypes")
        public String getDnByUid(String uid) throws NamingException {
            String url = this.getUrlWithoutSsl() + "/" + this.basedn;
    
            Hashtable<String, Object> env = new Hashtable<String, Object>(11);
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, url);
    
            String ret = "uid=" + uid;
            DirContext ctx = null;
    
            try {
                // Create initial context
                ctx = new InitialDirContext(env);
    
                SearchControls controls = new SearchControls();
                controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
    
                NamingEnumeration answer = ctx.search("", "(uid=" + uid + ")", controls);
    
                while (answer.hasMore()) {
                    SearchResult sr = (SearchResult) answer.next();
                    ret = sr.getNameInNamespace();
                    break;
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // Close the context when we're done
                ctx.close();
            }
    
            System.out.println("FULL DN:  " + ret);
    
            return ret;
        }
    
    }