Spring RestTemplate:SSL 握手失败
Spring RestTemplate: SSL handshake failure
我正在尝试使用具有基本身份验证的 restful ws。我没有将任何证书导入我的密钥库。当我使用 chrome 插件 Advance Rest client
对其进行测试时(使用带有 base64 编码的基本身份验证 username:pass)。我可以看到回复。到目前为止,一切都很好。
但是当我开发 Java 代码来使用这个 ws 时,我得到 SSL 握手失败:
org.springframework.web.client.ResourceAccessException: I/O error:
Received fatal alert: handshake_failure; nested exception is
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:377)
at test.Rest.main(Rest.java:37) Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
我的问题是:如果问题是因为我没有将证书导入我的密钥库,那么 Java 代码和插件应该不能一起工作。在这里,插件有效,但我的代码无效。
是什么原因?我的代码有问题吗?
下面是我的代码
RestTemplate restTemplate = new RestTemplate();
String plainCreds = "username:pass";
byte[] plainCredsBytes = plainCreds.getBytes(Charset.forName("US-ASCII") );
byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
ResponseEntity<String> response =
restTemplate.exchange("https://url",HttpMethod.GET,new
HttpEntity(headers),String.class);
这里是 link 日志文件:(我已经用 XXXXXX 替换了我的服务器名称)
http://www.filedropper.com/ssllog
在 运行ning 之后:openssl s_client -showcerts -tls1 -connect host:port
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
CONNECTED(00000164)
8088:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:.\ssl\s3_pkt.c:362:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 5 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1452011344
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
这是我 运行 命令 openssl s_client -connect server:port
时的输出
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
CONNECTED(00000164)
depth=0 C = US, ST = "XXXXXX", L = XXXXXX, O = XXXXXX, OU = xxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = US, ST = "XXXXXXX ", L = XXXXX, O = XXXXXX, OU = xxxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
i:/DC=intranet/DC=xxxx/CN=XXXXXX DEV Issuing CA
---
Server certificate
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXX.....
-----END CERTIFICATE-----
subject=/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
issuer=/DC=intranet/DC=XXX/CN=XXXXX DEV Issuing CA
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: XXXXX, P-256, 256 bits
---
SSL handshake has read 1895 bytes and written 443 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 568BF22A5CDBF103155264BBC056B272168AE0777CBC10F055705EB2DD907E5A
Session-ID-ctx:
Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1452012074
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
read:errno=0
从 javax.net.debug
日志中我可以看到您正在使用 Java 7 并且客户端解析为 TLSv1。从 openssl
输出表明您的服务器不支持 TLSv1。
TLS ver. 1.1 and 1.2 are disabled in Java 7 by default.
Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS
1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and
refuse to talk to TLS 1.1 or TLS 1.2 clients. For interoperability,
SunJSSE does not enable TLS 1.1 or TLS 1.2 by default for client
connections.
通过以下方式启用 TLSv1.1 和 TLSv1.2:
JVM 参数:
-Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
或从 Java 代码设置相同的 属性:
System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1");
或安装JCE Unlimited Strength policy files for Java 7。我不是 100% 确定这一步是否能解决问题,尽管安装 JCE 总是值得的,因为它允许 JVM 使用现有算法的更强版本。
更新 29/Sep/2016:
选项 1 和 2 中的协议顺序从好到坏(TLS 版本 1.2 到 1)。
如果您碰巧使用 Java 7 您需要明确告诉 Java 使用 TLSv1.2 协议。这是一个使用 Spring XML 配置的示例。
***In any Spring bean (i.e. a controller)***
import org.apache.http.client.HttpClient;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;
@Bean(name="client")
public HttpClient make() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContexts.custom().build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
}
***In XML configuration files***
<bean id="factory"
class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<property name="httpClient" ref="client"/>
</bean>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="requestFactory" ref="factory"/>
</bean>
你可以在没有 XML 的情况下做同样的事情,像这样:
RestTemplate restTemplate;
public HttpClient getHttpClient() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContexts.custom().build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
}
public void setUp() throws Exception {
restTemplate = new RestTemplate(
new HttpComponentsClientHttpRequestFactory(
getHttpClient()));
...
}
我正在尝试使用具有基本身份验证的 restful ws。我没有将任何证书导入我的密钥库。当我使用 chrome 插件 Advance Rest client
对其进行测试时(使用带有 base64 编码的基本身份验证 username:pass)。我可以看到回复。到目前为止,一切都很好。
但是当我开发 Java 代码来使用这个 ws 时,我得到 SSL 握手失败:
org.springframework.web.client.ResourceAccessException: I/O error:
Received fatal alert: handshake_failure; nested exception is
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:377)
at test.Rest.main(Rest.java:37) Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
我的问题是:如果问题是因为我没有将证书导入我的密钥库,那么 Java 代码和插件应该不能一起工作。在这里,插件有效,但我的代码无效。 是什么原因?我的代码有问题吗?
下面是我的代码
RestTemplate restTemplate = new RestTemplate();
String plainCreds = "username:pass";
byte[] plainCredsBytes = plainCreds.getBytes(Charset.forName("US-ASCII") );
byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
ResponseEntity<String> response =
restTemplate.exchange("https://url",HttpMethod.GET,new
HttpEntity(headers),String.class);
这里是 link 日志文件:(我已经用 XXXXXX 替换了我的服务器名称) http://www.filedropper.com/ssllog
在 运行ning 之后:openssl s_client -showcerts -tls1 -connect host:port
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
CONNECTED(00000164)
8088:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:.\ssl\s3_pkt.c:362:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 5 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1452011344
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
这是我 运行 命令 openssl s_client -connect server:port
时的输出WARNING: can't open config file: /usr/local/ssl/openssl.cnf
CONNECTED(00000164)
depth=0 C = US, ST = "XXXXXX", L = XXXXXX, O = XXXXXX, OU = xxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = US, ST = "XXXXXXX ", L = XXXXX, O = XXXXXX, OU = xxxxxx, CN = XXXXXXXXX.test.intranet, emailAddress = xxxxx@xxxxx
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
i:/DC=intranet/DC=xxxx/CN=XXXXXX DEV Issuing CA
---
Server certificate
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXX.....
-----END CERTIFICATE-----
subject=/C=US/ST=XXXXXXX /L=XXXXX/O=XXXXXX/OU=XXXXX/CN=XXXXXX.test.intranet/emailAddress=xxxxx@xxxxx
issuer=/DC=intranet/DC=XXX/CN=XXXXX DEV Issuing CA
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: XXXXX, P-256, 256 bits
---
SSL handshake has read 1895 bytes and written 443 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 568BF22A5CDBF103155264BBC056B272168AE0777CBC10F055705EB2DD907E5A
Session-ID-ctx:
Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1452012074
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
read:errno=0
从 javax.net.debug
日志中我可以看到您正在使用 Java 7 并且客户端解析为 TLSv1。从 openssl
输出表明您的服务器不支持 TLSv1。
TLS ver. 1.1 and 1.2 are disabled in Java 7 by default.
Although SunJSSE in the Java SE 7 release supports TLS 1.1 and TLS 1.2, neither version is enabled by default for client connections. Some servers do not implement forward compatibility correctly and refuse to talk to TLS 1.1 or TLS 1.2 clients. For interoperability, SunJSSE does not enable TLS 1.1 or TLS 1.2 by default for client connections.
通过以下方式启用 TLSv1.1 和 TLSv1.2:
JVM 参数:
-Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
或从 Java 代码设置相同的 属性:
System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1");
或安装JCE Unlimited Strength policy files for Java 7。我不是 100% 确定这一步是否能解决问题,尽管安装 JCE 总是值得的,因为它允许 JVM 使用现有算法的更强版本。
更新 29/Sep/2016:
选项 1 和 2 中的协议顺序从好到坏(TLS 版本 1.2 到 1)。
如果您碰巧使用 Java 7 您需要明确告诉 Java 使用 TLSv1.2 协议。这是一个使用 Spring XML 配置的示例。
***In any Spring bean (i.e. a controller)***
import org.apache.http.client.HttpClient;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;
@Bean(name="client")
public HttpClient make() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContexts.custom().build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
}
***In XML configuration files***
<bean id="factory"
class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<property name="httpClient" ref="client"/>
</bean>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="requestFactory" ref="factory"/>
</bean>
你可以在没有 XML 的情况下做同样的事情,像这样:
RestTemplate restTemplate;
public HttpClient getHttpClient() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContexts.custom().build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
}
public void setUp() throws Exception {
restTemplate = new RestTemplate(
new HttpComponentsClientHttpRequestFactory(
getHttpClient()));
...
}