Java jndi LDAPS自定义主机名验证程序和信任管理器

Java jndi LDAPS自定义主机名验证程序和信任管理器,java,ssl,ldap,jndi,jsse,Java,Ssl,Ldap,Jndi,Jsse,我们正在编写一个应用程序,它将连接到不同的LDAP服务器。对于每台服务器,我们可能只接受某个证书。该证书中的主机名不重要。当我们使用LDAP和STARTTLS时,这很容易,因为我们可以使用StartTLResponse.setHostnameVerifier(…-和StartTLResponse.negotiate(…)与匹配的SSLSocketFactory进行协商。但是,我们还需要支持LDAPS连接。Java本机支持这一点,但前提是默认Java密钥库信任服务器证书。虽然我们可以替换它,但我们

我们正在编写一个应用程序,它将连接到不同的LDAP服务器。对于每台服务器,我们可能只接受某个证书。该证书中的主机名不重要。当我们使用LDAP和STARTTLS时,这很容易,因为我们可以使用
StartTLResponse.setHostnameVerifier(…-
StartTLResponse.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() );
}
Hashtable env=new Hashtable();
put(Context.INITIAL\u Context\u工厂,“com.sun.jndi.ldap.LdapCtxFactory”);
env.put(Context.PROVIDER_URL,(encryption==SSL?“ldaps:/”:“ldap:/”)+host+“:”+port);
如果(加密==SSL){
//put(“java.naming.ldap.factory.socket”、“CustomSocketFactory”);
}
ctx=新的InitialLdapContext(env,null);
if(加密!=启动\u TLS)
tls=null;
否则{
tls=(StartTlsResponse)ctx.extendedOperation(newstarttlsrequest());
tls.setHostnameVerifier(hostnameVerifier);
协商(sslContext.getSocketFactory());
}

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

您应该传递自己的
SSLSocketFactory
子类的名称,并将其完全限定的名称传递到
java.naming.ldap.factory.socket“
env属性中,如下所述:

无法将任何特定参数传递给此类,请参阅中的实例化:

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


据我所知,在这个实现中使用
ldaps://
时,似乎没有任何主机名验证。如果您只信任信任信任管理器中的一个显式证书,这将弥补主机名验证的不足。

其他人也有同样的问题:我发现了一个非常丑陋的解决方案:

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

}
不太好,但很管用。
JNDI在这里应该更灵活…

我在上面的清单中有这行代码。但是,由于我只能指定一个类名,而不能指定某个实例,因此我无法向该类传递参数——这正是我所需要的。你看过问题了吗?如果是这样,请告诉我应该在哪里更详细。@SteffenHeil,对不起,我回答得太快了,我添加了更多详细信息。我必须连接到服务器S1,只接受证书C1,连接到服务器S2,只接受证书C2。使用CustomSocketFactory应该如何工作?在我的问题中,回答中提到的每一种可能性都是正确的。所以我否决了这个答案…@SteffenHeil,在编辑之前很公平,但是当答案告诉你在引用了实现你所追求的东西的代码(并且没有按照你想要的那样编码)之后你不能做什么事情时,它就不值得否决票(更多的是赞成票)。无论如何,我不会担心一票的问题…所以基本上,你在我的答案的继续部分给出了你自己的答案,你说你投了反对票…我从来没有说过你写的东西是错的,只是我的问题中已经提到了这些信息,但这些信息对解决问题没有任何帮助。这就是为什么我投了反对票。如果这不是否决票的有效理由,我会删除该否决票,但是EJPs upvote(这只是清除我的否决票)将保留,表决一个无用的答案,所以我不会,但是一旦你得到一个答案,在OpenJDK代码中告诉你为什么你不能做你想做的事情,除非你使用静态成员(你已经做过)或者类似JDNI的东西,我想这就是你问题的答案。然后,你接受了你自己的答案,这与我的建议非常明显。我不太在乎否决票,我也不介意你已经接受了自己的答案(因为你对
ThreadLocal
更具体)。我只是觉得从被否决的答案中得出的否决+接受自己的答案很奇怪。
Class socketFactoryClass = Obj.helper.loadClass(socketFactory);
Method getDefault =
    socketFactoryClass.getMethod("getDefault", new Class[]{});
Object factory = getDefault.invoke(null, new Object[]{});
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();
}