【问题标题】:Spring RestTemplate: SSL handshake failureSpring RestTemplate:SSL握手失败
【发布时间】:2016-01-02 06:44:22
【问题描述】:

我正在尝试使用具有基本身份验证的宁静 ws。我没有将任何证书导入我的密钥库。当我使用 chrome 插件 Advance Rest client 对其进行测试时(使用带有 base64 编码用户名的基本身份验证: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);

这是日志文件的链接:(我已将我的服务器名称替换为 XXXXXX) http://www.filedropper.com/ssllog

运行后: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

【问题讨论】:

  • Chrome 和您的应用可能使用不同的 Java,因此具有不同的设置。即使 Java 相同,也有数百万个原因导致它不起作用。然而,它在 Chrome 上工作是一个好的开始。启用 JVM 的 SSL 调试,例如:添加 -Djavax.net.debug=ssl:handshake:verbose 并将输出添加到您的问题中,其他调试参数可在此处获得 docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/…
  • 我会调查你的服务器,因为日志不包含 `ServerHello` 消息,就好像你的服务器没有运行 SSL。如果你有 openssl 尝试用openssl s_client -showcerts -tls1 -connect [your_host]:[port] 连接那里。另见smartjava.org/content/how-analyze-java-ssl-errors
  • 谢谢米哈尔。我添加了运行 openssl 的输出。请帮我。我是 ssl 和证书方面的新手
  • 你忽略了-tls1 openssl 开关。使用该选项发布输出。
  • 我发布了 2 个输出:一个是使用 openssl s_client -showcerts -tls1 -connect host:port。另一种是使用 openssl s_client -connect server:port。

标签: spring authentication ssl basic-authentication resttemplate


【解决方案1】:

javax.net.debug 日志中,我可以看到您使用的是 Java 7,并且客户端解析为 TLSv1。从openssl 输出,您的服务器不支持 TLSv1。

TLS ver. 1.1 and 1.2 are disabled in Java 7 by default.

尽管 Java SE 7 版本中的 SunJSSE 支持 TLS 1.1 和 TLS 1.2,默认情况下,这两个版本都没有为客户端连接启用。一些服务器没有正确实现前向兼容性,并且 拒绝与 TLS 1.1 或 TLS 1.2 客户端通信。为了互操作性, SunJSSE 默认不为客户端启用 TLS 1.1 或 TLS 1.2 连接。

通过以下方式启用 TLSv1.1 和 TLSv1.2:

  1. JVM 参数:

    -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
    
  2. 或者从 Java 代码中设置相同的属性:

    System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1");
    
  3. 或者安装JCE Unlimited Strength policy files for Java 7。我不能 100% 确定这一步是否能解决问题,尽管安装 JCE 总是值得的,同时它允许 JVM 使用现有算法的更强大版本。

2016 年 9 月 29 日更新

在选项 1 和 2 中,协议的顺序由好转坏(TLS 版本 1.2 到 1)。

【讨论】:

  • 感谢 Michal 的耐心帮助我。我们发现托管rest ws的服务器使用的是jdk8,而我的客户端使用的是jdk7。所以我们将服务器降级到jdk7。而且我还必须通过使用 -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 来强制 jdk 使用 TSLv1.2。它现在正在工作。
  • 在尝试使用 -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 之前。我还尝试为 jdk7 安装 JCE 无限强度策略。我已将 jdk/jre/lib/security 中的文件替换为 zip 文件中的文件。但它不起作用。
  • 欢迎大卫,感谢您的反馈和赞赏。
  • 我会像这样翻转顺序 -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1 这样它会先尝试更好的协议,最后回退到 v1。跨度>
【解决方案2】:

如果您碰巧使用的是 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()));
    ...
}

【讨论】:

    猜你喜欢
    • 2018-07-28
    • 2017-06-19
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多