Disabling SSL Certificate Validation in Spring RestTemplate

I am having two Spring-based web apps A and B, on two different machines.

I want to make an HTTPS call from web app A to web app B, however, I am using a self-signed certificate in Machine B. So my HTTPS request fails.

How can I disable HTTPS certificate validation when using RestTemplate in Spring? I want to disable validation because both web app A and B are within the internal network, but data transfer has to happen over HTTPS

227891 次浏览

您需要添加的是一个自定义 HostnameVerifier类绕过证书验证并返回 true

HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});

这需要适当地放置在您的代码中。

加上我对 cookie 的回应:

public static void main(String[] args) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("username", testUser);
params.add("password", testPass);
NullHostnameVerifier verifier = new NullHostnameVerifier();
MySimpleClientHttpRequestFactory requestFactory = new MySimpleClientHttpRequestFactory(verifier , rememberMeCookie);
ResponseEntity<String> response = restTemplate.postForEntity(appUrl + "/login", params, String.class);


HttpHeaders headers = response.getHeaders();
String cookieResponse = headers.getFirst("Set-Cookie");
String[] cookieParts = cookieResponse.split(";");
rememberMeCookie = cookieParts[0];
cookie.setCookie(rememberMeCookie);


requestFactory = new  MySimpleClientHttpRequestFactory(verifier,cookie.getCookie());
restTemplate.setRequestFactory(requestFactory);
}




public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory {


private final HostnameVerifier verifier;
private final String cookie;


public MySimpleClientHttpRequestFactory(HostnameVerifier verifier ,String cookie) {
this.verifier = verifier;
this.cookie = cookie;
}


@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
((HttpsURLConnection) connection).setSSLSocketFactory(trustSelfSignedSSL().getSocketFactory());
((HttpsURLConnection) connection).setAllowUserInteraction(true);
String rememberMeCookie = cookie == null ? "" : cookie;
((HttpsURLConnection) connection).setRequestProperty("Cookie", rememberMeCookie);
}
super.prepareConnection(connection, httpMethod);
}


public SSLContext trustSelfSignedSSL() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {


public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}


public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}


public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLContext.setDefault(ctx);
return ctx;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}


}




public class NullHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
@Bean
public RestTemplate restTemplate()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;


SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();


SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);


CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();


HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();


requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}

实际上,您需要做的两件事是使用 custom TrustStrategy that trusts all certs,并且还要使用 NoopHostnameVerifier ()来禁用主机名验证。下面是代码,包括所有相关的导入:

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;


public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}

您可以将其与 HTTPClientAPI 一起使用。

public RestTemplate getRestTemplateBypassingHostNameVerifcation() {
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);


}

我找到了一个简单的方法

    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);


RestTemplate restTemplate = new RestTemplate(requestFactory);

Imports used

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

要推翻默认策略,可以在连接 restTemplate 的类中创建一个简单的方法:

 protected void acceptEveryCertificate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {


TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};


restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
HttpClientBuilder
.create()
.setSSLContext(SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build())
.build()));
}

注意: 您肯定需要处理异常,因为这个方法只会抛出更多异常!

安全性: 禁用 https/TLS 证书主机名检查,下面的代码在 Spring 引导休息模板中工作

*HttpsURLConnection.setDefaultHostnameVerifier(
//SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
// * @deprecated (4.4) Use {@link org.apache.http.conn.ssl.NoopHostnameVerifier}
new NoopHostnameVerifier()
);*

完成禁用 SSL 主机名验证器的代码,

RestTemplate restTemplate = new RestTemplate();
//to disable ssl hostname verifier
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory() {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(new NoopHostnameVerifier());
}
super.prepareConnection(connection, httpMethod);
}
});

这个问题与 SSL 连接有关。当您尝试连接到某些资源 https 协议需要创建安全连接时。这意味着只有浏览器和网站服务器知道请求体中发送的数据。这种安全性是通过存储在网站上的 ssl 证书实现的,这些证书通过您的浏览器(或任何其他客户端,在我们的例子中是 Spring RestTemplate 和后面的 Apache Http Client)第一次连接到主机来下载。有 RSA256加密和许多其他很酷的东西周围。但在一天结束时: 如果证书没有注册或无效,您将看到证书错误(HTTPS 连接不安全)。修复证书错误的网站供应商需要购买它为特定的网站或以某种方式修复,例如 https://www.register.com/ssl-certificates

解决问题的正确方法

  • 注册 SSL 证书

这不是解决问题的正确方法

  • 从网站下载破损的 SSL 证书

  • import SSL certificate to Java cacerts (certificate storage)

    keytool -importcert -trustcacerts -noprompt -storepass changeit -alias name -keystore "C:\Program Files\Java\jdk-11.0.2\lib\security\cacerts" -file file.cer

肮脏(不安全)的方式如何解决问题

  • 使 RestTemplate 忽略 SSL 验证

      @Bean
    public RestTemplateBuilder restTemplateBuilder(@Autowired SSLContext sslContext) {
    return new RestTemplateBuilder() {
    @Override
    public ClientHttpRequestFactory buildRequestFactory() {
    return new HttpComponentsClientHttpRequestFactory(
    HttpClients.custom().setSSLSocketFactory(
    new SSLConnectionSocketFactory(sslContext
    , NoopHostnameVerifier.INSTANCE)).build());
    }
    };
    }
    
    
    @Bean
    public SSLContext insecureSslContext() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    return SSLContexts.custom()
    .loadTrustMaterial(null, (x509Certificates, s) -> true)
    .build();
    }
    

If you are using rest template, you can use this piece of code

    fun getClientHttpRequestFactory(): ClientHttpRequestFactory {
val timeout = envTimeout.toInt()
val config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build()


val acceptingTrustStrategy = TrustStrategy { chain: Array<X509Certificate?>?, authType: String? -> true }


val sslContext: SSLContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build()


val csf = SSLConnectionSocketFactory(sslContext)


val client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.setSSLSocketFactory(csf)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build()
return HttpComponentsClientHttpRequestFactory(client)
}


@Bean
fun getRestTemplate(): RestTemplate {
return RestTemplate(getClientHttpRequestFactory())
}

HttpClient > 4.3的 Java 代码示例

package com.example.teocodownloader;


import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;


public class Example {
public static void main(String[] args) {
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
}
}

顺便说一下,不要忘记在 pom 文件中添加以下依赖项:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>

You could find HttpClient < 4.3的 Java 代码示例.

@Bean
public RestTemplate restTemplate() throws Exception {
SSLContext sslContext = new SSLContextBuilder()
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(trustStore, trustStorePassword.toCharArray(), (cert, authType) -> sslTrustStrategy)
.build();
HostnameVerifier hostnameVerifier = sslTrustStrategy ? new NoopHostnameVerifier() :
SSLConnectionSocketFactory.getDefaultHostnameVerifier();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
HttpComponentsClientHttpRequestFactory httpComponentsHttpClientFactory =
new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(httpComponentsHttpClientFactory);
return restTemplate;
}

If sslTrustStrategy = true,

  1. 信任由于(cert,authType)-> sslTrustStrategy 而产生的所有证书
  2. 不要只信任证书中的主机,还要信任所有主机(由于 NoopHostnameVerifier 的原因,否则只信任证书中的主机)

另一种非常简单的方法是不导入任何 APACHE 或任何未知包。

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


private void ignoreCertificates() {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}


@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}


@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };


try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
     



}

并在 RestTemplate 之前设置 ignoreSecurities () :

   ignoreCertificates();
RestTemplate restTemplate = new RestTemplate();