jndi LDAPS自定义HostnameVerifier和TrustManager

时间:2012-02-22 12:35:49

标签: java ssl ldap jndi jsse

我们正在编写一个应用程序,它将连接到不同的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() );
}

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

2 个答案:

答案 0 :(得分:5)

对于其他人有同样的问题:我找到了一个非常难看的解决方案:

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

答案 1 :(得分:2)

您应该传递自己的SSLSocketFactory子类的名称,并将其完全限定的名称传递到"java.naming.ldap.factory.socket" env属性,如"Using Custom Sockets" section of the Java LDAP/SSL guide中所述:

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://时似乎没有任何主机名验证。如果您只信任信任管理器中的一个显式证书,那么无论如何都应该补偿主机名验证的缺失。