LdapTemplate忽略ssl证书

一、背景

最近做JAVA的LDAP操作,使用的是Spring的LdapTemplate,基本上一个bean注入就完成了LdapTemplate的初始化,正常连接389端口,现在要要试一下HTTPS的连接方式

spring.ldap:
  urls: ldap://ip:389
  base: dc=xxx,dc=com
  username: xxx
  password: xxx
  
@Bean
    public LdapTemplate firstLdapTemplate() {
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(url);
        contextSource.setBase(base);
        contextSource.setUserDn(username);
        contextSource.setPassword(password);
        contextSource.setPooled(false);
        contextSource.afterPropertiesSet(); // important

        LdapTemplate template = new LdapTemplate(contextSource);
        return template;
    }

二、采坑

把urls改成了:ldaps://xxx:636,启动报错,收到了如下错误:

Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)  
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)  
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)  
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)  
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)  
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)  
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)  
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)  
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)  
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)  
    at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:882)  
    at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)  
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)  
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)  
    at java.io.BufferedInputStream.read(BufferedInputStream.java:334)  
    at com.sun.jndi.ldap.Connection.run(Connection.java:853)  
    at java.lang.Thread.run(Thread.java:744)  
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)  
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)  
    at sun.security.validator.Validator.validate(Validator.java:260)  
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)  
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)  
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)  
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)  
    ... 12 more  
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)  
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)  
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)  
    ... 18 more  

说的是校验证书错误,运维搭建的ldap服务器用的是自签的证书,也没有给我证书,所以会失败,那我就忽略证书验证吧,这样总可以吧...,google了一下,有答案,心里很开心:

  LdapContextSource lcs = new LdapContextSource();
    lcs.setBase("[base]");
    lcs.setUserDn("[userDn]");
    lcs.setPassword("[password]");
    lcs.setPooled(false);
    lcs.setUrl("ldaps://[server-address]:636");

    DefaultTlsDirContextAuthenticationStrategy strategy = new DefaultTlsDirContextAuthenticationStrategy();
    strategy.setShutdownTlsGracefully(true);
    strategy.setSslSocketFactory(new CustomSSLSocketFactory());  // <-- not considered at all
    strategy.setHostnameVerifier(new HostnameVerifier(){

        @Override
        public boolean verify(String hostname, SSLSession session){

            return true;
        }
    });

    lcs.setAuthenticationStrategy(strategy);
    lcs.afterPropertiesSet();

CustomSSLSocketFactory是自定义的SSL工厂里面加载自己实现X509TrustManager,信任自签证书,然后运行,还是报错,瞬间怀疑啊,又是google一顿猛如虎的操作,有人遇到了跟我一样的问题(https://stackoverflow.com/questions/30546193/spring-ldapcontextsource-ignores-sslsocketfactory/30573130), 还好有人给出解决方法:

To fix the error use SimpleDirContextAuthenticationStrategy instead of DefaultTlsDirContextAuthenticationStrategy

但是没给出怎么用SimpleDirContextAuthenticationStrategy解决上面的问题,好吧,继续搜,找了一些答案,但是还是不行,还是继续报上面的错误,泪崩了,一直怀疑是不是自定义的CustomSSLSocketFactory与X509TrustManager有问题,而且网上的基本已经被我搜的差不多了,期间看到一个解决方案(http://java2db.com/jndi-ldap-programming/solution-to-sslhandshakeexception),我一直没试,因为不是LdapContextSource与ldaptemplate,用的是ldapContext,因为我无法通过ldapContext来构建ldaptemplate,所有就一直没试

由于怀疑CustomSSLSocketFactory与X509TrustManager有问题,然后网上又说这个方案是可以的,那就索性用来验证下我的CustomSSLSocketFactory与X509TrustManager到底有没有问题,写了个main函数,组织如下代码:

    String url = "ldaps://ip:636";
    String conntype = "simple";
    String AdminDn  = "xxx";
    String password = "xxx";
    Hashtable<String, String> environment = new Hashtable<String, String>();
    
    environment.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
    environment.put(Context.PROVIDER_URL,url); 
    environment.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName());        
    environment.put(Context.SECURITY_AUTHENTICATION,conntype);         
    environment.put(Context.SECURITY_PRINCIPAL,AdminDn);
    environment.put(Context.SECURITY_CREDENTIALS, password);
    
    ldapContext = new InitialDirContext(environment);
    
    System.out.println("Bind successful");

果然可以!!!!

既然这个可以,那么LdapContextSource应该也可以啊,只要按照上面,把socket注入到上下文中啊,可是LdapContextSource它压根就没给我设定的接口,没办法了,只能看源码了,看看LdapContextSource的url,name,password是怎么初始化进去的

AbstractContextSource类的getAuthenticatedEnv:

protected Hashtable<String, Object> getAuthenticatedEnv(String principal, String credentials) {
		// The authenticated environment should always be rebuilt.
		Hashtable<String, Object> env = new Hashtable<String, Object>(getAnonymousEnv());
		setupAuthenticatedEnvironment(env, principal, credentials);
		return env;
	}

然后看了下getAnonymousEnv是protected,完美,是不是可以继承LdapContextSource,然后重写getAnonymousEnv方法,立马测试下:

public class SSLLdapContextSource extends LdapContextSource {
    public Hashtable<String, Object> getAnonymousEnv(){
        Hashtable<String, Object> anonymousEnv = super.getAnonymousEnv();
        anonymousEnv.put("java.naming.security.protocol", "ssl");
        anonymousEnv.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName());
        anonymousEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        return anonymousEnv;
    }
}

@Bean
public LdapTemplate secondLdapTemplate() {
    //此处用     SSLLdapContextSource
    SSLLdapContextSource contextSource = new SSLLdapContextSource();
    contextSource.setUrl(url);
    contextSource.setBase(base);
    contextSource.setUserDn(username);
    contextSource.setPassword(password);
    contextSource.setPooled(false);
    contextSource.afterPropertiesSet(); // important
    LdapTemplate template = new LdapTemplate(contextSource);
    return template;
}

欧了,,只需简单的继承下LdapContextSource,前后加起来折腾了一天时间,o(╥﹏╥)o

附上CustomSSLSocketFactory:

public class CustomSSLSocketFactory extends SSLSocketFactory
{
    private SSLSocketFactory socketFactory;
    public CustomSSLSocketFactory()
    {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[]{ new DummyTrustmanager()}, new SecureRandom());
            socketFactory = ctx.getSocketFactory();
        } catch ( Exception ex ){ ex.printStackTrace(System.err);  }
    }
    public static SocketFactory getDefault(){
        return new CustomSSLSocketFactory();
    }
    @Override
    public String[] getDefaultCipherSuites()
    {
        return socketFactory.getDefaultCipherSuites();
    }
    @Override
    public String[] getSupportedCipherSuites()
    {
        return socketFactory.getSupportedCipherSuites();
    }
    @Override
    public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException
    {
        return socketFactory.createSocket(socket, string, num, bool);
    }
    @Override
    public Socket createSocket(String string, int num) throws IOException, UnknownHostException
    {
        return socketFactory.createSocket(string, num);
    }
    @Override
    public Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException
    {
        return socketFactory.createSocket(string, num, netAdd, i);
    }
    @Override
    public Socket createSocket(InetAddress netAdd, int num) throws IOException
    {
        return socketFactory.createSocket(netAdd, num);
    }
    @Override
    public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException
    {
        return socketFactory.createSocket(netAdd1, num, netAdd2, i);
    }


    /**
     * 证书
     */
    public static class DummyTrustmanager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException
        {
        }
        public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException
        {
        }
        public X509Certificate[] getAcceptedIssuers()
        {
            return new java.security.cert.X509Certificate[0];
        }

    }

posted on 2019-07-01 16:14  木兆  阅读(4153)  评论(4编辑  收藏  举报

导航