jndi LDAPS 自定义 HostnameVerifier 和 TrustManager

2022-01-17 00:00:00 ssl ldap java jsse jndi

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

We are writing an application that shall connect to different LDAP servers. For each server we may only accept a certain certificate. The hostname in that certificate shall not matter. This is easy, when we use LDAP and STARTTLS, because we can use StartTlsResponse.setHostnameVerifier(..-) and use StartTlsResponse.negotiate(...) with a matching SSLSocketFactory. However we also need to support LDAPS connections. Java supports this natively, but only if the server certificate is trusted by the default java keystore. While we could replace that, we still cannot use different keystores for different servers.

现有的连接代码如下:

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

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

We could add out own CustomSocketFactory, but how to pass information to that?

推荐答案

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

For others have the same problem: I found a very ugly solution for my case:

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 在这里应该更灵活...

Not nice, but it works. JNDI should be more flexible here...

相关文章