我们正在编写一个应用程序,它将连接到不同的LDAP服务器。对于每个服务器,我们只能接受一个特定的证书。证书中的主机名无关紧要。当我们使用LDAP和STARTTLS时,这很容易,因为我们可以使用StartTlsResponse.setHostnameVerifier(..-)和带有匹配的SSLSocketFactory的StartTlsResponse.negotiate(...)。但是,我们还需要支持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,但是如何向它传递信息呢?
发布于 2012-03-15 20:08:03
对于其他有同样问题的人:我为我的情况找到了一个非常丑陋的解决方案:
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在这里应该更灵活...
发布于 2012-02-22 21:06:09
您应该传递自己的SSLSocketFactory子类的名称,并将其完全限定名传递到"java.naming.ldap.factory.socket"环境属性中,如"" 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://时,似乎没有任何主机名验证。如果您只信任信任管理器中的一个显式证书,则无论如何这应该会弥补缺少主机名验证的不足。
发布于 2021-11-30 15:32:10
我已经构建了我自己的解决方案,但它远非完美。实际上,我担心由于在javax.naming中的不幸实现,没有完美的解决方案。
我的SelectiveLdapSslSocketFactory包含一个将主机映射到不同SSLSocketFactories的静态映射。当调用任何createSocket(...)方法时,都会将调用委托给相应的SSLSocketFactory。它还包含一个用于不带任何映射的主机的defaultSslSocketFactory,也包含在getDefaultCipherSuites()和getSupportedCipherSuites()方法中。我不确定它是否正确,但它在我的情况下工作得很好,所以如果你愿意的话,可以测试它:
public class SelectiveLdapSslSocketFactory extends SSLSocketFactory {
private static SSLSocketFactory defaultSslSocketFactory;
private static final Map<String, SSLSocketFactory> hostToSslSocketFactoryMap = new HashMap<>();
{
try {
defaultSslSocketFactory = <yourOwnDefaultSslSocketFactory>;
} catch (Exception ex) {
Logger.warn(ex, "Couldn't initialize a defaultSslSocketFactory for LDAP connections!");
}
}
public static SSLSocketFactory getRegisteredSslSocketFactory(String host) {
return hostToSslSocketFactoryMap.get(host);
}
public static void registerSslSocketFactory(String host, SSLSocketFactory sslSocketFactory) {
hostToSslSocketFactoryMap.put(host, sslSocketFactory);
}
public static void deregisterSslSocketFactory(String host) {
hostToSslSocketFactoryMap.remove(host);
}
public SelectiveLdapSslSocketFactory() {
}
public static SocketFactory getDefault() {
return new SelectiveLdapSslSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return defaultSslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return defaultSslSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(s, host, port, autoClose);
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
SSLSocketFactory sslSocketFactory = Objects.requireNonNullElse(hostToSslSocketFactoryMap.get(host), defaultSslSocketFactory);
return sslSocketFactory.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocketFactory sslSocketFactory = getSslSocketFactory(host);
return sslSocketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
SSLSocketFactory sslSocketFactory = getSslSocketFactory(address);
return sslSocketFactory.createSocket(address, port, localAddress, localPort);
}
private SSLSocketFactory getSslSocketFactory(InetAddress inetAddress) {
SSLSocketFactory sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getCanonicalHostName(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostName(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = hostToSslSocketFactoryMap.get(Objects.requireNonNullElse(inetAddress.getHostAddress(), ""));
if (sslSocketFactory == null) {
sslSocketFactory = defaultSslSocketFactory;
}
}
}
return sslSocketFactory;
}
}然后,您可以像这样使用它:
...
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host01, sslSocketFactory01);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host02, sslSocketFactory02);
SelectiveLdapSslSocketFactory.registerSslSocketFactory(host03, sslSocketFactory03);
props.put("java.naming.ldap.factory.socket", SelectiveLdapSslSocketFactory.class.getName());https://stackoverflow.com/questions/9394864
复制相似问题