Java通過SSL忽略Certificate訪問LDAP服務器【轉】


最近負責AD賬戶同步,遇到證書問題。

搜索后都說從AD服務器拿下證書,導入到java的cacerts中,嘗試多次后無效。

絕望之際,看到 https://www.iteye.com/blog/chnic-2065877 的一篇文章,想起請求https接口時做的繞過證書操作,發現基本一個原理。

以下為大神的原文:

 

 

前兩天工作遇到一個基於C/S結構的LDAP+SSL訪問的問題,由於LDAP的服務器都是內網服務器,所以不需要去進行certificate。在網上搜了一下,找到了個solution分享給大家。

 

由於默認的Java over SSL是需要certificate,對於一些不需要證書的case,如果只是簡簡單單的在初始化Context的時候加上如下的語句

 1 props.put(Context.SECURITY_PROTOCOL, "ssl");  

 

你就會收到如下的異常:

 1 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  
 2     at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)  
 3     at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)  
 4     at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)  
 5     at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)  
 6     at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)  
 7     at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)  
 8     at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)  
 9     at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)  
10     at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)  
11     at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)  
12     at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:882)  
13     at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)  
14     at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)  
15     at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)  
16     at java.io.BufferedInputStream.read(BufferedInputStream.java:334)  
17     at com.sun.jndi.ldap.Connection.run(Connection.java:853)  
18     at java.lang.Thread.run(Thread.java:744)  
19 Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
20     at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)  
21     at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)  
22     at sun.security.validator.Validator.validate(Validator.java:260)  
23     at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)  
24     at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)  
25     at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)  
26     at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)  
27     ... 12 more  
28 Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
29     at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)  
30     at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)  
31     at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)  
32     ... 18 more

 

這一大串的異常信息用一句簡單的話來概括就是你的Java client通過SSL來訪問LADP server的時候,需要證書來做certificate,但是在我們本地並沒有這樣的東西,所以創建連接失敗。

 

如何在建立連接的時候忽略certificate這一步呢?在我們的Java代碼里需要做如下的事情,首先我們需要創建一個我們自己的TrustManagerh和SSLSocketFactory來替代默認的SSLSocketFactory

import java.security.cert.CertificateException;  
import java.security.cert.X509Certificate;  
  
import javax.net.ssl.X509TrustManager;  
  
public class LTSTrustmanager implements X509TrustManager {  
  
    @Override  
    public void checkClientTrusted(X509Certificate[] arg0, String arg1)  
            throws CertificateException {  
          
    }  
  
    @Override  
    public void checkServerTrusted(X509Certificate[] arg0, String arg1)  
            throws CertificateException {  
          
    }  
  
    @Override  
    public X509Certificate[] getAcceptedIssuers() {  
        return new java.security.cert.X509Certificate[0];  
    }  
  
}  
import java.io.IOException;  
import java.net.InetAddress;  
import java.net.Socket;  
import java.net.UnknownHostException;  
import java.security.SecureRandom;  
  
import javax.net.SocketFactory;  
import javax.net.ssl.SSLContext;  
import javax.net.ssl.SSLSocketFactory;  
import javax.net.ssl.TrustManager;  
  
public class LTSSSLSocketFactory extends SSLSocketFactory {  
  
    private SSLSocketFactory socketFactory;  
      
    public LTSSSLSocketFactory() {  
        try {  
            SSLContext ctx = SSLContext.getInstance("TLS");  
            ctx.init(null, new TrustManager[]{ new LTSTrustmanager()}, new SecureRandom());  
            socketFactory = ctx.getSocketFactory();  
        } catch ( Exception ex ) {   
            ex.printStackTrace(System.err);  
        }  
    }  
      
    public static SocketFactory getDefault(){  
        return new LTSSSLSocketFactory();  
    }  
      
    @Override  
    public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {  
        return null;  
    }  
  
    @Override  
    public String[] getDefaultCipherSuites() {  
        return socketFactory.getDefaultCipherSuites();  
    }  
  
    @Override  
    public String[] getSupportedCipherSuites() {  
        return socketFactory.getSupportedCipherSuites();  
    }  
  
    @Override  
    public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {  
        return socketFactory.createSocket(arg0, arg1);  
    }  
  
    @Override  
    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {  
        return socketFactory.createSocket(arg0, arg1);  
    }  
  
    @Override  
    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {  
        return socketFactory.createSocket(arg0, arg1, arg2, arg3);  
    }  
  
    @Override  
    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {  
        return socketFactory.createSocket(arg0, arg1, arg2, arg3);  
    }  
}  

 

 

這兩個類里有幾句代碼要解釋一下。

SSLContext ctx = SSLContext.getInstance("TLS");  
ctx.init(null, new TrustManager[]{ new LTSTrustmanager()}, new SecureRandom());  
socketFactory = ctx.getSocketFactory();  

這里的TLS其實是一個protocol。TLS的全稱是Transport Layer Security Protocol,至於這個協議具體是干嘛用的,自己google啦。酷 接下來的兩句就是通過自己dummy的TrustManager來初始化我們的SSLSocketFactory。值得多提的一句就是getDefault方法一定要有,因為在SSL建立連接的時候他需要通過這個方法來獲取SSLSocketFactory的實例。

 

至於我們自己dummy的TrustManager我們只需要實現getAcceptedIssuers這個方法,讓他返回一個X509Certificate的數組即可。

 

public X509Certificate[] getAcceptedIssuers() {  
    return new java.security.cert.X509Certificate[0];  
}  

上述的一切都做好之后,我們需要把我們dummy的class配置到LdapContextt當中。

 

 

Properties props = new Properties();  
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");  
props.setProperty(Context.PROVIDER_URL, "ldap.provider.url=ldap://XXXXXX:636");  
props.put("java.naming.ldap.factory.socket", "LTSSSLSocketFactory");  
props.put(Context.SECURITY_PROTOCOL, "ssl");  
props.setProperty(Context.URL_PKG_PREFIXES, "com.sun.jndi.url");  
props.setProperty(Context.REFERRAL, "ignore");  
props.setProperty(Context.SECURITY_AUTHENTICATION, "simple");         
props.setProperty(Context.SECURITY_PRINCIPAL, "xxxxx");  
props.setProperty(Context.SECURITY_CREDENTIALS, "xxxxxxx");  
LdapContext ctx = new InitialLdapContext(props, null);  

這里注意我們新配置的java.naming.ldap.factory.socket是需要包名+類名的,比如

props.put("java.naming.ldap.factory.socket", "com.xxx.LTSSSLSocketFactory");  

 就此我們完成了全部的工作,Java over SSL再也不需要certificate。

 

 

 

 

我是這樣用的

 

 

原來引用的keystore注釋掉了,加上自定義的ssl類

 

另外: ladp 進行批量同步時,25個左右會報一次錯,停留3S左右,繼續進行就不會報錯了。

 


免責聲明!

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



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