HTTPClient 4.5.1 访问https
2016-07-06 00:00
316 查看
摘要: 由于对方提供的接口是基于https 协议,并且是自己生成的证书,处理中遇到一些问题,这里记录一下。
EXCEPTION 1 :Host name does not match the certificate subject provided by the peer
//关闭主机名的验证
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
EXCEPTION 2:The server failed to respond with a valid HTTP response
防火墙的原因,妈蛋公司的防火墙已经不止一次挑战我的极限了,今天要是早想到这点现在就早闪人了,擦!不管怎么说还是感谢原博主,这里仅作记录。
Apache HttpComponents/Clientで、SSL証明書の検証、ホスト名の検証を無効化する
Java
開発中のテスト環境とかで、よくあるネタ?的な。
Apache HttpComponents/Clientを使って、SSL自己署名証明書を使って通信したり、ホスト名の検証を無効化する方法について、メモとして書いておきます。
Apache HttpComponents - Apache HttpComponents
いつも微妙にやり方を忘れて、毎度毎度調べることになっているので、備忘録的にと。
もちろん、ご利用はテストなどでの範囲で、ですね。
ちなみに、java.net.URLConnectionを使う場合については、以前書きました。
JavaでSSL証明書の検証無効化、ホスト名検証の無効化…とデバッグ - CLOVER
テストコードには、JUnit+AssertJを利用。
また、テスト用のSSLを有効にしたWebサーバーは、Ubuntu LinuxでインストールできるSSLを有効にしたApacheとしました。
ではでは、書いていってみます。
SSL証明書の検証で、NGとなる場合ですね。自己署名証明書だと、これに遭遇すると思います。
テストコードは、こんな感じ。
TrustStrategyの実装である、TrustSelfSignedStrategyを使用することで自己署名証明書でもOKになりますよ、と。
で、こちらを使ってHttpClientを作成する、と。
これで、証明書のエラーは回避できます。
このケースだと、このようなスタックトレースが得られます。
先ほどのSSL証明書の検証を行わないだけのコードは、このような感じになっています。
が、これだと今回用意した環境では、ホスト名が証明書とアクセスパスで不一致のため、エラーになると。
そこで、ここではHostnameVerifierを使用します。
NoopHostnameVerifierを使用すると、ホスト名の検証を無効化できます。
SSL証明書の検証と、ホスト名の検証を全部合わせたコードは、このような形になります。
HostnameVerifierは、setSSLHostnameVerifierで指定します。
とりあえず、目的は達成できましたよっと。
この場合は、setSSLSocketFactoryを使用します。
注:除了该博客提供的两种方式以外,其实还有其他的解决方案,原理都是忽略或者所有认证都设置为受信,在处理这个问题走了不少弯路。
EXCEPTION 1 :Host name does not match the certificate subject provided by the peer
//关闭主机名的验证
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
EXCEPTION 2:The server failed to respond with a valid HTTP response
防火墙的原因,妈蛋公司的防火墙已经不止一次挑战我的极限了,今天要是早想到这点现在就早闪人了,擦!不管怎么说还是感谢原博主,这里仅作记录。
Apache HttpComponents/Clientで、SSL証明書の検証、ホスト名の検証を無効化する
Java開発中のテスト環境とかで、よくあるネタ?的な。
Apache HttpComponents/Clientを使って、SSL自己署名証明書を使って通信したり、ホスト名の検証を無効化する方法について、メモとして書いておきます。
Apache HttpComponents - Apache HttpComponents
いつも微妙にやり方を忘れて、毎度毎度調べることになっているので、備忘録的にと。
もちろん、ご利用はテストなどでの範囲で、ですね。
ちなみに、java.net.URLConnectionを使う場合については、以前書きました。
JavaでSSL証明書の検証無効化、ホスト名検証の無効化…とデバッグ - CLOVER
準備
Apache HttpComponentsって、バージョンでけっこうコロコロAPIが変わるので、「これ!」というのは言いづらいのですが、今回はApache HttpComponents(というかClient)の4.5.1を対象にします。<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.1</version> </dependency>
テストコードには、JUnit+AssertJを利用。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.2.0</version> <scope>test</scope> </dependency>
また、テスト用のSSLを有効にしたWebサーバーは、Ubuntu LinuxでインストールできるSSLを有効にしたApacheとしました。
サンプルコードで使うimport文
以降のサンプルコードでは、以下のimport文があることを前提にしています。import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy;
ではでは、書いていってみます。
事象その1、自己署名証明書なのでエラーになる
通信時に、こんなスタックトレースが出力されるようなケース。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:1949) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979) at sun.security.ssl.Handshaker.process_record(Handshaker.java:914) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
SSL証明書の検証で、NGとなる場合ですね。自己署名証明書だと、これに遭遇すると思います。
テストコードは、こんな感じ。
@Test public void testSelfSignedFailure() throws IOException { try (CloseableHttpClient client = HttpClients.createDefault()) { HttpGet get = new HttpGet("https://localhost"); assertThatThrownBy(() -> { try (CloseableHttpResponse response = client.execute(get)) { assertThat(response.getStatusLine().getStatusCode()) .isEqualTo(HttpStatus.SC_OK); assertThat(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)) .contains("apache2"); } }) .isInstanceOf(SSLHandshakeException.class) .hasMessageContaining("sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"); } }
自己署名証明書でもOKにするには
これをパスするには、以下のようなコードを書いてSSLContextを作成します。TrustStrategy trustStrategy = new TrustSelfSignedStrategy(); SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(trustStrategy) .build();
TrustStrategyの実装である、TrustSelfSignedStrategyを使用することで自己署名証明書でもOKになりますよ、と。
で、こちらを使ってHttpClientを作成する、と。
try (CloseableHttpClient client = HttpClients .custom() .setSSLContext(sslContext) .build()) { HttpGet get = new HttpGet("https://localhost");
これで、証明書のエラーは回避できます。
事象その2、ホスト名の検証でエラーになる
これだけではまだエラーになる場合が、SSL証明書に書かれているホスト名が、実際にアクセスしているホストと合わない場合。このケースだと、このようなスタックトレースが得られます。
javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=e611e15f9c9d) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:465) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:395) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353) at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134) at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353) at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380) at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
先ほどのSSL証明書の検証を行わないだけのコードは、このような感じになっています。
@Test
public void testHostNameFailure() throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {TrustStrategy trustStrategy = new TrustSelfSignedStrategy(); SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(trustStrategy) .build();
assertThatThrownBy(() -> {try (CloseableHttpClient client = HttpClients .custom() .setSSLContext(sslContext) .build()) { HttpGet get = new HttpGet("https://localhost");
try (CloseableHttpResponse response = client.execute(get)) {
assertThat(response.getStatusLine().getStatusCode())
.isEqualTo(HttpStatus.SC_OK);
assertThat(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8))
.contains("apache2");
}
}
})
.isInstanceOf(SSLPeerUnverifiedException.class)
.hasMessageContaining("Host name 'localhost' does not match the certificate subject provided by the peer (CN=e611e15f9c9d)");
}
が、これだと今回用意した環境では、ホスト名が証明書とアクセスパスで不一致のため、エラーになると。
そこで、ここではHostnameVerifierを使用します。
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
NoopHostnameVerifierを使用すると、ホスト名の検証を無効化できます。
SSL証明書の検証と、ホスト名の検証を全部合わせたコードは、このような形になります。
@Test
public void testSelfSignedSuccess() throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {TrustStrategy trustStrategy = new TrustSelfSignedStrategy(); SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(trustStrategy) .build();
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;try (CloseableHttpClient client = HttpClients .custom() .setSSLContext(sslContext) .setSSLHostnameVerifier(hostnameVerifier) .build()) {
HttpGet get = new HttpGet("https://localhost");
try (CloseableHttpResponse response = client.execute(get)) {
assertThat(response.getStatusLine().getStatusCode())
.isEqualTo(HttpStatus.SC_OK);
assertThat(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8))
.contains("apache2");
}
}
}
HostnameVerifierは、setSSLHostnameVerifierで指定します。
try (CloseableHttpClient client = HttpClients .custom() .setSSLContext(sslContext) .setSSLHostnameVerifier(hostnameVerifier) .build()) {
とりあえず、目的は達成できましたよっと。
別解
SSLContextとSSLConnectionSocketFactoryの組み合わせでも、同様のことが実現できます。
@Test
public void testSelfSignedSuccessAnother() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, IOException {TrustStrategy trustStrategy = new TrustSelfSignedStrategy(); SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(trustStrategy) .build();
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnSocketFactory = new
SSLConnectionSocketFactory(sslContext,
hostnameVerifier);
try (CloseableHttpClient client = HttpClients .custom() .setSSLSocketFactory(sslConnSocketFactory) .build()) {
HttpGet get = new HttpGet("https://localhost");
try (CloseableHttpResponse response = client.execute(get)) {
assertThat(response.getStatusLine().getStatusCode())
.isEqualTo(HttpStatus.SC_OK);
assertThat(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8))
.contains("apache2");
}
}
}
この場合は、setSSLSocketFactoryを使用します。
try (CloseableHttpClient client = HttpClients .custom() .setSSLSocketFactory(sslConnSocketFactory) .build()) {
相关文章推荐
- 访问Nginx发生SSL connection error的一种情况
- MySQL复制解决方案(Replication Solutions)
- Linux实现https方式访问站点
- HTTPS科普扫盲帖
- HTTPS的七个误解
- ASP.NET MVC Web API HttpClient简介
- Apache SSL服务器配置SSL详解
- 配置apache默认使用ssl的方法
- World Wide Web Publishing 服务尝试删除 IIS 所有的 SSL 配置数据失败的几种方法
- Apache、SSL、MySQL和PHP平滑无缝地安装
- mysql通过ssl的方式生成秘钥具体生成步骤
- Centos 5下配置https服务器的方法
- apache https配置详细步骤讲解
- 使用httpclient实现免费的google翻译api
- windows服务器中检测PHP SSL是否开启以及开启SSL的方法
- python简单实现基于SSL的IRC bot实例
- Tomcat ssl报错Connector attribute SSLCertificateFile must be defined when using SSL with APR解决方法
- PHP封装的HttpClient类用法实例