【问题标题】:LDAP Error 49 from Java JNDI Connection来自 Java JNDI 连接的 LDAP 错误 49
【发布时间】:2014-11-13 23:00:09
【问题描述】:

* 检查下面的完整解决方案*

我被 Java LDAP 连接问题困扰了好几天。

这是我连接到 LDAP 服务器的方法:

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;
}

出于安全和披露原因,我省略了 XXXXX、YYY 和 ZZ 的“dc”,并更改了 LDAP 服务器的 IP。

我在 PHP 软件 (GLPI) 中使用了相同的组合,它的工作原理非常棒。但是,看在上帝的份上,Java 不能接受这个 LDAP 配置,总是给我这个错误:

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)

完整的 dn 是这样的:

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

变量user用“tiagoadami”填充,变量“argument”用纯文本密码填充。

这很烦人。我的密码是正确的,我正在使用用户名“tiagoadami”和密码对每个应用程序进行身份验证。我现在别无选择。谁能帮帮我?

【问题讨论】:

  • 这是什么类型的 LDAP 服务器(Active Directory、OpenLdap ......)?
  • 您的用户的 DN 看起来不正确,它缺少 uid 和 dc 之间的对象类型。
  • 这是一个运行 Linux 的 OpenLdap 3。问题是:匿名登录是可以的,并且 - 希望是安全错误 - 当我看到这个 DN 对我的用户来说是正确的时,我可以匿名获取所有 LDAP 树。将相同的 DN 设置到 GLPI 中效果很好。我用一个名为 LdapExplorerTool2 的 Windows 工具尝试了这个 DN,但也没有用。我错过了什么?
  • 用户名 DN 通常类似于:cn=username,ou=people,dc=widgetworld,dc=toybox,dc=com 或者在您的情况下是 uid=tiagoadami,ou=people,dc=xxxx ,dc=yyy,dc=zz 或 ou=people 可以是 cn=users (对于 AD),但在用户名之后有一些东西来限定它是什么类型的对象。继续使用 jxplorer 之类的东西并进行匿名绑定,然后搜索您的 uid,您将看到可用于绑定的完整 DN。
  • 我已经使用了 LdapExplorerTool2,并且设置的 DN 与我以匿名身份登录时在 LDAP 树中显示的完全相同。没用。现在我收到错误:“LDAP:错误代码 49 - SASL(-13):找不到用户:数据库中没有秘密”。我迷路了……

标签: java authentication ldap jndi


【解决方案1】:

我很高兴得到评论的每个人的帮助。如果没有你的帮助,我会被困住。我发现 LDAP 服务器不允许仅与基本 DN 绑定。

经过拼命尝试,我可以使用树的完整路径进行连接:

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

代替:

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

Sooooooooo looooooooong...通过这 2 个类我能够解决 所有我的 LDAP 问题。我将它们更改为在树内搜索并获取给定 UID 的完整 DN。它们适用于任何有同样问题的人:

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;
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-11
    相关资源
    最近更新 更多