一个 LDAP 的 AD 身份验证失败但通过另一个 LDAP

AD authentication with one LDAP failed but passed with another LDAP

从以下网站,我找到了执行 java AD 身份验证的代码。

http://java2db.com/jndi-ldap-programming/solution-to-sslhandshakeexception

代码如下:

MySSLSocketFactory.java

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

public class MySSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory socketFactory;
    public MySSLSocketFactory() {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());
            socketFactory = ctx.getSocketFactory();
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
        }
    }
    public static SocketFactory getDefault() {
        return new MySSLSocketFactory();
    }
    @Override
    public String[] getDefaultCipherSuites() {
        return socketFactory.getDefaultCipherSuites();
    }
    @Override
    public String[] getSupportedCipherSuites() {
        return socketFactory.getSupportedCipherSuites();
    }
    @Override
    public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {
        return socketFactory.createSocket(socket, string, num, bool);
    }
    @Override
    public Socket createSocket(String string, int num) throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, num);
    }
    @Override
    public Socket createSocket(String string, int num, InetAddress netAdd, int i)
            throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, num, netAdd, i);
    }
    @Override
    public Socket createSocket(InetAddress netAdd, int num) throws IOException {
        return socketFactory.createSocket(netAdd, num);
    }
    @Override
    public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {
        return socketFactory.createSocket(netAdd1, num, netAdd2, i);
    }
}

DummyTrustmanager.java

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class DummyTrustmanager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException
    {
    }
    public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException
    {
    }
    public X509Certificate[] getAcceptedIssuers()
    {
    return new java.security.cert.X509Certificate[0];
    }

}

TestAD.java

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestAD {

    public static void main(String[] args) {
        try {
            //String url = "ldaps://abc.company.com:636";
            String url = "ldaps://xyz.group.com:636";
            String conntype = "simple";
            // String id = "abc@abc.company.com";
            String id = "xyz.group.com";
            //String password = "abcpassword";
            String password = "xyzpassword";
            Hashtable<String, String> environment = new Hashtable<String, String>();
            environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            environment.put(Context.PROVIDER_URL, url);
            environment.put("java.naming.ldap.factory.socket", "MySSLSocketFactory");
            environment.put(Context.SECURITY_AUTHENTICATION, conntype);
            environment.put(Context.SECURITY_PRINCIPAL, id);
            environment.put(Context.SECURITY_CREDENTIALS, password);
            DirContext ldapContext = new InitialDirContext(environment);
            System.out.println("Bind successful");
        } catch (Exception exception) {
            exception.printStackTrace();
        }

    }
}

我公司有几个子公司,他们有自己的 LDAP。当我 运行 TestAD 针对我公司 ABC 的 LDAP 时,它工作正常。但是当我 运行 它针对子公司 XYZ 的 LDAP 时,我得到以下异常:

javax.naming.CommunicationException: simple bind failed: xyz.group.com:636
[Root exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.]
        at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:219)
        at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2791)
        at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
        at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192)
        at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:210)
        at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:153)
        at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:83)
        at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
        at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
        at javax.naming.InitialContext.init(InitialContext.java:244)
        at javax.naming.InitialContext.<init>(InitialContext.java:216)
        at javax.naming.directory.InitialDirContext.<init>(InitialDirContext.java:101)
        at TestAD.main(TestAD.java:26)
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:931)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
        at com.sun.jndi.ldap.Connection.run(Connection.java:877)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching xyz.group.com found.
        at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:214)
        at sun.security.util.HostnameChecker.match(HostnameChecker.java:96)
        at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:459)
        at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:1125)
        at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1092)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
        ... 12 more

失败的原因可能是什么?我没有将 ABC 或 XYZ 的证书导入我的信任库。为什么它适用于 ABC 但不适用于 XYZ?难道 XYZ 需要我的证书?

这是由端点识别算法引起的,它会检查您配置中的主机名是否与远程 LDAPS TLS 服务器证书中的主机名相匹配,以及这些主机名是否有效.

鉴于此错误 No subject alternative DNS name matching xyz.group.com found,肯定是 "xyz.group.com" 与您的 ldap 服务器证书不匹配,而 "abc.company.com" 匹配。

Java 8u181 对 core-libs/javax.naming 进行了一些更改,并更新了安全 LDAP 连接的处理方式(参见 release note):

Endpoint identification has been enabled on LDAPS connections.

To improve the robustness of LDAPS (secure LDAP over TLS) connections, endpoint identification algorithms have been enabled by default.

Note that there may be situations where some applications that were previously able to successfully connect to an LDAPS server may no longer be able to do so. Such applications may, if they deem appropriate, disable endpoint identification using a new system property: com.sun.jndi.ldap.object.disableEndpointIdentification.

Define this system property (or set it to true) to disable endpoint identification algorithms.

禁用端点识别算法是一种解决方法,长期解决方案是修复服务器证书,使其与 "xyz.group.com" 主机名匹配。