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];
        }

    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM