2012-02-22 100 views
3

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

现有的连接代码如下:

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,但如何对信息传递给?

回答

3

对于其他人有同样的问题:我发现我的情况非常丑陋的解决方案:

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

+0

所以基本上,你已经产生了你自己的答案,在我的答案的延续中,你说你已经低估了... – Bruno 2012-03-27 09:57:00

+0

I从来没有说过你写的东西是错的,只是我的问题中已经提到了这些信息,并没有以任何方式帮助解决这个问题。这就是为什么我低估了。如果这不是一个有效的降低投票的提议,我会删除那个downvote,但是然后EJPs upvote(这只是为了清除我的downvote)留下来,投了一个无用的答案,所以我不会。 – 2012-03-28 10:29:17

+0

正如我所说的,在编辑之前就已经足够了,但是一旦你得到了一个答案,它显示了OpenJDK代码中的原因,除非你使用静态成员(你已经完成)或者像JDNI之类的东西,否则你为什么不能做你想做的事情,我认为这是对你的问题的回答。然后,你已经接受了你自己的答案,这非常明显地符合我的建议。我不关心downvote,我不介意你已经接受了你自己的答案(因为你对ThreadLocal更具体)。我只是发现downvoting +接受自己的回答来自downvoted的答案。 – Bruno 2012-03-28 11:17:16

1

你应该通过自己的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://似乎没有任何主机名验证。如果您只信任信任管理器中的一个显式证书,则无论如何这应该补偿缺乏主机名验证。

+0

我在我的上面列表中有这行代码。但是,因为我只能指定一个类名而不是某个实例,所以我无法将参数传递给该类 - 这正是我所需要的。你读过这个问题了吗?如果是这样,告诉我我应该更详细的位置。 – 2012-02-22 15:57:32

+0

@SteffenHeil,对不起,我回答得太快,我已经添加了更多细节。 – Bruno 2012-02-22 16:33:50

+0

Upvoted unfoldined downvote – EJP 2012-02-23 23:27:03