5

我们正在编写一个应连接到不同 LDAP 服务器的应用程序。对于每台服务器,我们可能只接受某个证书。该证书中的主机名无关紧要。当我们使用 LDAP 和 STARTTLS 时,这很容易,因为我们可以使用StartTlsResponse.setHostnameVerifier(..-)和使用StartTlsResponse.negotiate(...)匹配的SSLSocketFactory. 但是,我们还需要支持 LDAPS 连接。Java 本机支持这一点,但前提是服务器证书受默认 ​​java 密钥库信任。虽然我们可以替换它,但我们仍然不能为不同的服务器使用不同的密钥库。

现有的连接代码如下:

Hashtable<String,String> env = new Hashtable<String,String>();
env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
env.put( Context.PROVIDER_URL, ( encryption == SSL ? "ldaps://" : "ldap://" ) + host + ":" + port );
if ( encryption == SSL ) {
    // env.put( "java.naming.ldap.factory.socket", "CustomSocketFactory" );
}
ctx = new InitialLdapContext( env, null );
if ( encryption != START_TLS )
    tls = null;
else {
    tls = (StartTlsResponse) ctx.extendedOperation( new StartTlsRequest() );
    tls.setHostnameVerifier( hostnameVerifier );
    tls.negotiate( sslContext.getSocketFactory() );
}

我们可以添加 own CustomSocketFactory,但是如何传递信息呢?

4

3 回答 3

7

对于其他人也有同样的问题:我为我的案例找到了一个非常丑陋的解决方案:

import javax.net.SocketFactory;

public abstract class ThreadLocalSocketFactory
  extends SocketFactory
{

  static ThreadLocal<SocketFactory> local = new ThreadLocal<SocketFactory>();

  public static SocketFactory getDefault()
  {
    SocketFactory result = local.get();
    if ( result == null )
      throw new IllegalStateException();
    return result;
  }

  public static void set( SocketFactory factory )
  {
    local.set( factory );
  }

  public static void remove()
  {
    local.remove();
  }

}

像这样使用它:

env.put( "java.naming.ldap.factory.socket", ThreadLocalSocketFactory.class.getName() );
ThreadLocalSocketFactory.set( sslContext.getSocketFactory() );
try {
  ctx = new InitialLdapContext( env, null );
} finally {
  ThreadLocalSocketFactory.remove();
}

不好,但它的工作原理。JNDI在这里应该更灵活......

于 2012-03-15T12:08:02.703 回答
4

您应该传递自己的SSLSocketFactory子类的名称并将其完全限定的名称传递给"java.naming.ldap.factory.socket"env 属性,如 Java LDAP/SSL 指南的使用自定义套接字”部分所述

env.put("java.naming.ldap.factory.socket", "example.CustomSocketFactory");

您不能将任何特定参数传递给此类,请参见实例化com.sun.jndi.ldap.Connection.createSocket(...)

Class socketFactoryClass = Obj.helper.loadClass(socketFactory);
Method getDefault =
    socketFactoryClass.getMethod("getDefault", new Class[]{});
Object factory = getDefault.invoke(null, new Object[]{});

如果您需要其他参数,您可能不得不使用静态成员或 JNDI(通常不理想)。

ldaps://据我所知,不幸的是,在此实现中使用时似乎没有任何主机名验证。如果您只信任信任管理器中的一个显式证书,这应该可以弥补缺少主机名验证的问题。

于 2012-02-22T13:06:09.257 回答
0

我已经建立了适合我的自己的解决方案,但它远非完美。我实际上担心由于javax.naming.

MySelectiveLdapSslSocketFactory包含将主机映射到不同 SSLSocketFactories 的静态 Map。当调用任何createSocket(...)方法时,调用将委托给相应的SSLSocketFactory. 它还包含一个defaultSslSocketFactory用于没有任何映射的主机以及在getDefaultCipherSuites()andgetSupportedCipherSuites()方法中。我不确定,如果它是正确的,但在我的情况下它工作正常,所以如果你愿意,可以测试一下:

public class SelectiveLdapSslSocketFactory extends SSLSocketFactory {

    private static SSLSocketFactory defaultSslSocketFactory;
    private static final Map<String, SSLSocketFactory> hostToSslSocketFactoryMap = new HashMap<>();
    
    {
        try {
            defaultSslSocketFactory = <yourOwnDefaultSslSocketFactory>;
        } catch (Exception ex) {
            Logger.warn(ex, "Couldn't initialize a defaultSslSocketFactory for LDAP connections!");
        }
    }

    public static SSLSocketFactory getRegisteredSslSocketFactory(String host) {
        return hostToSslSocketFactoryMap.get(host);
    }

    public static void registerSslSocketFactory(String host, SSLSocketFactory sslSocketFactory) {
        hostToSslSocketFactoryMap.put(host, sslSocketFactory);
    }

    public static void deregisterSslSocketFactory(String host) {
        hostToSslSocketFactoryMap.remove(host);
    }

    public SelectiveLdapSslSocketFactory() {
    }

    public static SocketFactory getDefault() {
        return new SelectiveLdapSslSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return defaultSslSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return defaultSslSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
        return sslSocketFactory.createSocket(s, host, port, autoClose);
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
        return sslSocketFactory.createSocket(host, port);
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
        return sslSocketFactory.createSocket(host, port, localHost, localPort);
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        SSLSocketFactory sslSocketFactory = getSslSocketFactory(host);
        return sslSocketFactory.createSocket(host, port);
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        SSLSocketFactory sslSocketFactory = getSslSocketFactory(address);
        return sslSocketFactory.createSocket(address, port, localAddress, localPort);
    }

    private SSLSocketFactory getSslSocketFactory(InetAddress inetAddress) {
        SSLSocketFactory sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getCanonicalHostName(), ""));
        if (sslSocketFactory == null) {
            sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostName(), ""));
            if (sslSocketFactory == null) {
                sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostAddress(), ""));
                if (sslSocketFactory == null) {
                    sslSocketFactory = defaultSslSocketFactory;
                }
            }
        }
        return sslSocketFactory;
    }
}

然后你可以像这样使用它:

...
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host01, sslSocketFactory01);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host02, sslSocketFactory02);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host03, sslSocketFactory03);

props.put("java.naming.ldap.factory.socket", SelectiveLdapSslSocketFactory.class.getName());
于 2021-11-30T15:32:09.840 回答