`
chnic
  • 浏览: 225837 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java通过SSL忽略Certificate访问LDAP服务器

    博客分类:
  • Java
阅读更多

前两天工作遇到一个基于C/S结构的LDAP+SSL访问的问题,由于LDAP的服务器都是内网服务器,所以不需要去进行certificate。在网上搜了一下,找到了个solution分享给大家。

 

由于默认的Java over SSL是需要certificate,对于一些不需要证书的case,如果只是简简单单的在初始化Context的时候加上如下的语句

 

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

 

你就会收到如下的异常:

 

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

 这一大串的异常信息用一句简单的话来概括就是你的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。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics