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的不足。