我知道,关于这个问题有很多不同的问题和很多答案...但我无法理解......
我有:ubuntu-9.10-desktop-amd64 + NetBeans6.7.1从"关闭"安装.代表.我需要通过HTTPS连接到某个站点.为此我使用Apache的HttpClient.
从教程我读到:
"一旦正确安装了JSSE,通过SSL进行安全的HTTP通信
就像普通的HTTP通信一样简单." 还有一些例子:
HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.verisign.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); }
到现在为止,我写道:
HttpClient client = new HttpClient(); HttpMethod get = new GetMethod("https://mms.nw.ru"); //get.setDoAuthentication(true); try { int status = client.executeMethod(get); System.out.println(status); BufferedInputStream is = new BufferedInputStream(get.getResponseBodyAsStream()); int r=0;byte[] buf = new byte[10]; while((r = is.read(buf)) > 0) { System.out.write(buf,0,r); } } catch(Exception ex) { ex.printStackTrace(); }
结果我有一组错误:
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:1627) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:204) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:198) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:994) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:142) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:533) at sun.security.ssl.Handshaker.process_record(Handshaker.java:471) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:904) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1132) at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:643) at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:78) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828) at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116) at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096) at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398) at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323) at simpleapachehttp.Main.main(Main.java:41) 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:302) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:205) at sun.security.validator.Validator.validate(Validator.java:235) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:147) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:230) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:270) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:973) ... 17 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:191) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:255) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:297) ... 23 more
我该怎么做才能创建最简单的SSL连接?(可能没有KeyManager和信任经理等.)
https://mms.nw.ru使用自签名证书,该证书显然不包含在默认的信任管理器集中.
您需要执行以下操作之一:
使用接受任何证书的TrustManager配置SSLContext(参见下文)
使用包含证书的相应信任库来配置SSLContext
将该站点的证书添加到默认的Java信任库.
这是一个示例程序,它创建一个(几乎毫无价值的)SSL上下文,它接受任何证书:
import java.net.URL; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SSLTest { public static void main(String [] args) throws Exception { // configure the SSLContext with a TrustManager SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); SSLContext.setDefault(ctx); URL url = new URL("https://mms.nw.ru"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }); System.out.println(conn.getResponseCode()); conn.disconnect(); } private static class DefaultTrustManager 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 null; } } }
https://mms.nw.ru可能使用非由证书颁发机构颁发的证书.因此,您需要将证书添加到受信任的Java密钥存储区,如无法找到所请求目标的有效证书路径所述:
在使用以https协议运行的启用SSL的服务器的客户端上工作时,如果服务器证书不是由证书颁发机构颁发,而是由自签名或由自己签发,则可能会出现"无法找到所请求目标的有效证书路径"的错误私人CMS.
不要惊慌.如果客户端是用Java编写的,那么您需要做的就是将服务器证书添加到受信任的Java密钥存储区.您可能想知道如何无法访问安装服务器的计算机.有一个简单的程序可以帮助你.请下载Java程序并运行
% java InstallCert _web_site_hostname_此程序打开与指定主机的连接并启动SSL握手.它打印出现的错误的异常堆栈跟踪,并显示服务器使用的证书.现在,它会提示您将证书添加到受信任的KeyStore.
如果您改变主意,请输入"q".如果您确实要添加证书,请输入"1"或其他数字以添加其他证书,甚至是CA证书,但您通常不希望这样做.一旦做出选择,程序将显示完整的证书,然后将其添加到当前目录中名为"jssecacerts"的Java KeyStore.
要在程序中使用它,请将JSSE配置为将其用作信任库,或将其复制到$ JAVA_HOME/jre/lib/security目录中.如果您希望所有Java应用程序将证书识别为受信任而不仅仅是JSSE,您还可以覆盖该目录中的cacerts文件.
毕竟,JSSE将能够与主机完成握手,您可以通过再次运行程序来验证.
要获得更多详细信息,您可以查看Leeland的博客不再"无法找到要求目标的有效证书路径"
除了Pascal Thivent的正确答案,另一种方法是从Firefox保存证书(查看证书 - >详细信息 - >导出)或openssl s_client
将其导入信任库.
只有在有办法验证证书时才应该这样做.如果失败了,在第一次连接时执行此操作,如果证书在后续连接中意外更改,则至少会给您一个错误.
要在信任库中导入它,请使用:
keytool -importcert -keystore truststore.jks -file servercert.pem
默认情况下,默认信任库应该是,lib/security/cacerts
并且密码应该是changeit
,有关详细信息,请参阅JSSE参考指南.
如果您不想全局允许该证书,但仅限于这些连接,则可以为其创建一个SSLContext
:
TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream("/.../truststore.jks"); ks.load(fis, null); // or ks.load(fis, "thepassword".toCharArray()); fis.close(); tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null);
然后,你需要实现一个如果将其设置为Apache HTTP客户端3.x中SecureProtocolSocketFactory
使用此SSLContext
.(有例子在这里).
Apache HTTP Client 4.x(除了最早的版本)直接支持传递SSLContext
.
Apache HttpClient 4.5方式:
org.apache.http.ssl.SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); sslContextBuilder.loadTrustMaterial(new org.apache.http.conn.ssl.TrustSelfSignedStrategy()); SSLContext sslContext = sslContextBuilder.build(); org.apache.http.conn.ssl.SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new org.apache.http.conn.ssl.DefaultHostnameVerifier()); HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(sslSocketFactory); httpClient = httpClientBuilder.build();
注意:org.apache.http.conn.ssl.SSLContextBuilder
已弃用并且org.apache.http.ssl.SSLContextBuilder
是新的(通知conn
在后者的包名称中缺失).
从 http://hc.apache.org/httpclient-3.x/sslguide.html:
Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.whatever.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); }
凡MySSLSocketFactory例子可以找到这里.它引用了一个TrustManager
,您可以修改它以信任所有内容(尽管您必须考虑这一点!)
对于Apache HttpClient 4.5+和Java8:
SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial((chain, authType) -> true).build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] {"SSLv2Hello", "SSLv3", "TLSv1","TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient client = HttpClients.custom() .setSSLSocketFactory(sslConnectionSocketFactory) .build();
但是,如果您的HttpClient使用ConnectionManager来寻求连接,例如:
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build();
在HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
没有任何效果,问题没有得到解决。
因为HttpClient使用指定的connectionManager来查找连接,并且指定的connectionManager尚未注册我们自定义的SSLConnectionSocketFactory。若要解决此问题,应在connectionManager中注册自定义的SSLConnectionSocketFactory。正确的代码应如下所示:
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder.create() .register("http",PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build()); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build();