问题遇到的现象和发生背景
最近公司有一个稍微旧一点的项目,使用Java 8和SpringBoot 1.5.8.RELEASE版本,由于SpringBoot 1.0版本不支持WebClient, 所以只能使用RestTemplate进行连接(公司现有的使用SpingBoot 2.0版本以上的其它项目都在使用WebClient)。使用RestTemplate访问公司使用http协议的服务器时,没有任何错误,可以得到200的响应;但是在当前项目里使用RestTemplate访问https的服务器(使用self-signed certificate)时,总会报“javax.net.ssl.SSLException: java.net.SocketException: Connection reset”这个错误。这个项目中之前也用RestTemplate连接过https的服务器,我试了他们之前用到的那个URL,没有报错,但是连我现在要用到的这个URL时就会出现问题。即便如此,可能也不是我要连接的服务器方面的错误,具体在下面会谈到。
问题相关代码,请勿粘贴截图
以下配置尝试过都会产生SocketException: Connection Rest
关闭SSL验证之后的RestTemplate的配置:
@Bean(name = "RestConnector")
public RestTemplate createRestTemplate1() {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
HostnameVerifier hostnameVerifier = (s, session) -> true;
SSLContext sslContext = null;
try {
sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (Exception e) {
e.printStackTrace();
}
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
}
将公司的证书导入到RestTemplate的配置:
@Bean(name = "JKSConnector")
public RestTemplate getRdsJKS1() {
InputStream ins = null;
RestTemplate restTemplate = null;
try {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
HostnameVerifier hostnameVerifier = (s, session) -> true;
ins = keyStorePath.getInputStream();
String tempPath ="/tmp/client.jks";
File targetFile = new File(tempPath);
FileUtils.copyInputStreamToFile(ins, targetFile);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(new SSLContextBuilder()
.loadTrustMaterial(targetFile, keyStorePassWord.toCharArray(), acceptingTrustStrategy)
.build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setSSLHostnameVerifier(hostnameVerifier).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
restTemplate = new RestTemplate(factory);
} catch (IOException | NoSuchAlgorithmException | KeyStoreException | CertificateException | KeyManagementException e) {
LOG.error("Exception occurred when building Resttemplate: -{}", e);
} finally {
if(ins != null) {
try {
ins.close();
} catch (IOException e) {
LOG.error("Exception when closing input stream: -{}", e);
}
}
}
return restTemplate;
}
这个项目中之前用到的关闭SSL验证之后的配置:
@Bean(name = "RDSRestConnector")
public RestTemplate createRestTemplate() {
TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, UNQUESTIONING_TRUST_MANAGER, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
System.out.println(e);
}
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
LOG.info("Creating custom RestTemplate successful ");
return new RestTemplate(requestFactory);
}
运行结果及报错内容
访问带有self-signed证书的https的服务器时报一下错误:
SEVERE: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://*****.net/*****/**/*******": java.net.SocketException: Connection reset; nested exception is javax.net.ssl.SSLException: java.net.SocketException: Connection reset] with root cause
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:475)
at sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:469)
at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:159)
at sun.security.ssl.SSLTransport.decode(SSLTransport.java:110)
at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1279)
at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1188)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:401)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:373)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:587)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:167)
at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:78)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:652)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
at com.*****.service.TestConnectService.executeGet2(TestConnectService.java:88)
at com.*****.controller.TestRdsController.execute(TestRdsController.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.******.CorsFilterConfig.doFilter(CorsFilterConfig.java:43)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:208)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
我的解答思路和尝试过的方法
我觉得配置代码没有问题,所以为了检查这个错误,我自己新建了一个springboot的测试项目,这个测试项目中用到的dependencies和springboot版本都和出问题的项目保持一致。结果是同样的代码,在测试项目中连接公司的https服务器没有任何问题,甚至不用配置RestTemplate也可以使用(直接new)。只有在出问题的这个项目中才会一直有connection reset这个错误。
这个问题尝试过不同的解决方案:
- 最开始配置看见这个错误的时候,由于之前在公司的其它项目中遇到过因为证书不可靠出现的SSL验证问题(不同点是使用WebClient),所以想在RestTemplate中关闭SSL的验证,配置代码如以上所示,最后仍然报相同的错误。
- 配置公司的proxy,这个proxy在其它项目中也有用到,但是在现在这个项目中报HandshakeException。
- 将公司的证书(.crt)转换成一个jks文件,导入到RestTemplate中,配置过程如之前代码所示。仍然有connection reset错误。
- 咨询过相关团队,他们让我下载了公司的证书,生成pem文件导入到Java 8的JVM中,仍然报错。之前在最开始有提到测试项目,在测试项目中即使使用Java 11也可以正常连接https服务器(本地的Java 11中没有安装pem证书,仅在Java 8中有)。
- 尝试在http header中设置长连接和短连接,报错。
- 尝试升级springboot版本到2.0,使用和其它项目中用到的一样的WebClient和相同的配置尝试连接,仍然报connection reset错误。
- 尝试报错后进行retry,报错。
- IntelliJ IDEA中没有配置proxy.
- 怀疑是SSL版本问题,尝试在socketFactory配置了对应的版本(从postman中得到信息使用“TLSv1.3”), 结果connection reset:
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContextBuilder.build(), new String[]{"TLSv1","TLSv1.1","TLSv1.2","SSLv3"}, null, NoopHostnameVerifier.INSTANCE);
- 尝试使用RestTmplate的不同方法,如getForObject, getForEntity, exchange等,报相同的错误。
- 尝试询问当初写这个项目的团队是否配置了什么东西阻止了访问,对方说应该没有。项目中其它地方也有用到RestTemplate来访问https服务,经过测试,他们之前项目里用到的https地址不会报错,而我现在要用的这个地址就会出现问题。
- Postman中不需要添加任何证书也可以直接访问要连接的endpoint得到结果。
我想要达到的结果
这个问题是在于相同的代码在我新建的测试项目中完全可以使用,测试项目所用的依赖和spring版本都和老项目完全相同,甚至RestTemplate直接new不用配置也可以使用,但在放到这个目前需要完成的老项目中就会一直报connection reset错误。
希望可以知道怎么解决这个问题,肯定是项目中哪里出现了问题,但没看到相关的配置啊,希望得到一些指导!
以上错误均发生在本地(local),不在云端。