jndi LDAPS自定义HostnameVerifier和TrustManager

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

现有的连接代码如下:

 Hashtable env = new Hashtable(); 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 ,但是如何将信息传递给它?

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

 import javax.net.SocketFactory; public abstract class ThreadLocalSocketFactory extends SocketFactory { static ThreadLocal local = new ThreadLocal(); 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在这里应该更灵活……

您应该传递自己的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://似乎没有任何主机名validation。 如果您只信任信任管理器中的一个显式证书,那么无论如何都应该弥补主机名validation的不足。