Spring resttemplate 2 way ssl using spring boot - spring

I'm consuming a RESTful Web Service using Spring Boot and restTemplate. The service is secured with 2 way ssl. So fare I have made this code to configure the connection. The code works but what I'm looking for is the best way to implement 2 way SSL with springs restTemplate and httpClient or another alternative using Spring Boot
#Configuration()
public class RestClientConfig {
private static final Logger log = LoggerFactory.getLogger(RestClientConfig.class);
#Bean()
#Qualifier("SSLRestOperations")
public RestOperations restOperations(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
return restTemplate;
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
// timeout
requestFactory.setReadTimeout(60 * 1000);
requestFactory.setConnectTimeout(60 * 1000);
return requestFactory;
}
#Bean
public HttpClient httpClient(#Value("${keystore.file}") Resource file, #Value("${keystore.pass}") Password password) throws Exception {
String keystorePassword = password.getDescrambled();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
TrustManagerFactory tmf =TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream instream = file.getInputStream();
InputStream instreamKey = file.getInputStream();
try {
keyStore.load(instreamKey, keystorePassword.toCharArray());
trustStore.load(instream, keystorePassword.toCharArray());
kmf.init(keyStore, keystorePassword.toCharArray());
tmf.init(trustStore);
} finally {
instream.close();
instreamKey.close();
}
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
.loadKeyMaterial(trustStore, keystorePassword.toCharArray()).build();
SSLSocketFactory socketFactory = sslcontext.getSocketFactory();
SSLSocket socket = (SSLSocket) socketFactory.createSocket();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1.2" }, null, new NoopHostnameVerifier());
return HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()) // HostnameVerifier NoopHostnameVerifier
.setSSLSocketFactory(sslsf)
.build();
}

Related

Configure SSL with Webflux Webclient using Apache HttpComponents

I am trying to migrate from restTemplate to webClient.
Everything was fine until I reached restTemplate config with ClientHttpRequestFactory.
I paste here the old and the new codes.
------Old code with restTemplate-------
private HttpComponentsClientHttpRequestFactory buildRequestFactory() {
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyHost, proxyPort),
new UsernamePasswordCredentials(proxyUser, proxyPassword));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = clientBuilder
.setSSLSocketFactory(connectionFactory)
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, request, context);
}
})
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return requestFactory;
}
#Bean(name = "gatewayRestTemplate")
public RestTemplate gatewayRestTemplateConfig() {
RestTemplate restTemplate = new RestTemplate(converters());
restTemplate.setRequestFactory(buildRequestFactory());
return restTemplate;
}
------New code with webClient-------
private ClientHttpConnector buildClientConnector() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
org.apache.hc.core5.http.HttpHost proxy = new org.apache.hc.core5.http.HttpHost(proxyHost, proxyPort);
org.apache.hc.client5.http.auth.CredentialsProvider credsProvider = new org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider();
((org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider) credsProvider).setCredentials(new org.apache.hc.client5.http.auth.AuthScope(proxyHost, proxyPort),
new org.apache.hc.client5.http.auth.UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new DefaultAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory connectionFactory =
new org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
org.apache.hc.core5.http.config.Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
// .<org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory>create().register("https", connectionFactory)
.<ConnectionSocketFactory>create().register("https", connectionFactory)
// .register("http", new PlainConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpAsyncClient client = clientBuilder
.setConnectionManager((AsyncClientConnectionManager) connectionManager)
.setRoutePlanner(new org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner(proxy) {
#Override
protected org.apache.hc.core5.http.HttpHost determineProxy(org.apache.hc.core5.http.HttpHost target, org.apache.hc.core5.http.protocol.HttpContext context) throws org.apache.hc.core5.http.HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, context);
}
})
.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return connector;
}
#Primary
#Bean(name = "defaultWebClient")
public WebClient defaultWebClientConfig() {
WebClient webClient = WebClient.builder()
.clientConnector(buildClientConnector())
.build();
return webClient;
}
When I run the project, I get this exception:
Caused by: java.lang.ClassCastException: class org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager cannot be cast to class org.apache.hc.client5.http.nio.AsyncClientConnectionManager (org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager and org.apache.hc.client5.http.nio.AsyncClientConnectionManager are in unnamed module of loader 'app')
Based on Migration to Apache HttpClient 5.0 async APIs, I solved my problem. The idea is to use ClientTlsStrategyBuilder when setting sslContext.
private ClientHttpConnector buildClientConnector() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
org.apache.hc.core5.http.HttpHost proxy = new org.apache.hc.core5.http.HttpHost(proxyHost, proxyPort);
org.apache.hc.client5.http.auth.CredentialsProvider credsProvider = new org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider();
((org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider) credsProvider).setCredentials(new org.apache.hc.client5.http.auth.AuthScope(proxyHost, proxyPort),
new org.apache.hc.client5.http.auth.UsernamePasswordCredentials(proxyUser, proxyPassword.toCharArray()));
clientBuilder.useSystemProperties();
clientBuilder.setProxy(proxy);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
clientBuilder.setProxyAuthenticationStrategy(new DefaultAuthenticationStrategy());
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ServiceException(GlobalErrorMessage.INTERNAL_SERVER_ERROR);
}
PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(ClientTlsStrategyBuilder.create()
.setSslContext(sslContext)
.setHostnameVerifier(new NoopHostnameVerifier())
.build())
.build();
CloseableHttpAsyncClient client = clientBuilder
.setConnectionManager(connectionManager)
.setRoutePlanner(new org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner(proxy) {
#Override
protected org.apache.hc.core5.http.HttpHost determineProxy(org.apache.hc.core5.http.HttpHost target, org.apache.hc.core5.http.protocol.HttpContext context) throws org.apache.hc.core5.http.HttpException {
if (target.getHostName().equals(noproxy)) {
return null;
}
return super.determineProxy(target, context);
}
})
.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return connector;
}
#Primary
#Bean(name = "defaultWebClient")
public WebClient defaultWebClientConfig() {
WebClient webClient = WebClient.builder()
.clientConnector(buildClientConnector())
.build();
return webClient;
}
If you want to use HttpClient connector. Please use below code for
webclient. The above answers any of them not worked, below solution is
working fine for me.
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create().secure(t ->
t.sslContext(sslContext) );
WebClient webClient = WebClient.builder()
.baseUrl("any-url")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

Spring boot using https only - Test failing

I have spring boot - angular application
My server port is 1234 (example).
But now I have setup ssl
Steps:
created self signed cert using keytool and added my p12 cert file into resources folder
updated application.properties
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:myfile.p12
server.ssl.key-store-password=somepasswordfromenv
server.ssl.key-alias=myalias
server.ssl.enabled=true
http.port=8080
server.port=8443
Started app and tested
#SpringBootTest(classes = AppWithoutBeansApplication.class, webEnvironment = WebEnvironment.DEFINED_PORT)
class AppControllerTest {
#Value("${server.ssl.key-store}")
private Resource trustStore;
#Value("${server.ssl.key-store-password}")
private String trustStorePassword;
#Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect()
throws ClientProtocolException, IOException {
String urlOverHttps = "https://localhost:8443/";
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
ResponseEntity<String> response
= new RestTemplate(requestFactory).exchange(
urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
...
Error:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:8443/": Certificate for doesn't match any of the subject alternative names: []; nested exception is javax.net.ssl.SSLPeerUnverifiedException: Certificate for doesn't match any of the subject alternative names: []
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:746)
Don't understand how my integration test works
All answers looked similar, but this one worked
Ignore SSL certificate validation when using Spring RestTemplate
#Bean
public RestTemplate restTemplate() throws GeneralSecurityException {
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslsf).register("http", new PlainConnectionSocketFactory()).build();
BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(
socketFactoryRegistry);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.setConnectionManager(connectionManager).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
Looks like this part made the difference
*Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslsf).register("http", new PlainConnectionSocketFactory()).build();*
DO NOT USE THIS
// #Bean
// public RestTemplate nonsslrestTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
// 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);
// return new RestTemplate(requestFactory);
// }

Spring RestTemplate certificate 403 Forbidden: [no body]

I use p12 certificate with RestTemplate to call an external API.
RestTemplate:
final SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(keyStoreFile.getURL(), keyPassword.toCharArray(), (X509Certificate[] chain, String authType) -> true)
.build();
final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, INSTANCE);
final HttpClient httpClient = custom()
.setSSLSocketFactory(socketFactory)
.setMaxConnTotal(1000)
.setMaxConnPerRoute(40)
.build();
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
And the call:
HttpEntity<String> entity = new HttpEntity<>(httpHeaders);
final ResponseEntity<MyList> response = restTemplate.exchange("https://REMOTE_URI/sameObjects", GET, entity, MyList.class);
I tried header with differents values (User-Agent, Host, ..) and ResponseEntity<Object> , but I have always the same error :
org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden: [no body]
I can access it with Postman with the p12 certificate.
Thank you for your help
I found the solution, I change RestTemplate :
public RestTemplate getRestTemplate() {
try {
final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(keyStoreFile.getInputStream(), keyPassword.toCharArray());
final SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(keyStoreFile.getURL(), keyPassword.toCharArray(), (X509Certificate[] chain, String authType) -> true)
.loadKeyMaterial(keyStore, keyPassword.toCharArray())
.build();
final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, INSTANCE);
final HttpClient httpClient = custom()
.setSSLSocketFactory(socketFactory)
.build();
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
final RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
return restTemplate;
} catch (IOException e) {
log.error("....", e);
throw new ApiException(e);
} catch (Exception e) {
log.error("....", e);
throw new ApiException(e);
}
}

Spring Boot Rest Template Keep Connection Alive

I have a Spring Boot application that is creating a request to an external system. The external system is responding after some time, 3-4 minutes. I would like to keep the connection open until i receive an response from the remote API. I tried using webflux, i tried setup the connection timeout for my application in application.yml file. I could make the application to wait for a response more than 2 minutes. Some code that i have tried
#Configuration
public class RestConfigurations {
#Bean(name = "restTemplate")
public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder) {
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
// Error handler
restTemplate.setErrorHandler(new CustomRestTemplateErrorHandler());
// Media converters
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(
MediaType.APPLICATION_JSON,
MediaType.TEXT_PLAIN,
MediaType.TEXT_HTML));
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int connectionTimeout = 15*60000; // milliseconds
int socketTimeout = 15*60000; // milliseconds
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(connectionTimeout)
.setConnectionRequestTimeout(connectionTimeout)
.setSocketTimeout(socketTimeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.setKeepAliveStrategy(connectionKeepAliveStrategy())
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
public HttpComponentsClientHttpRequestFactory getHttpClientFactory() {
HttpComponentsClientHttpRequestFactory httpClientFactory = new
HttpComponentsClientHttpRequestFactory(
HttpClients.createDefault()
);
httpClientFactory.setConnectTimeout(15 * 600000);
httpClientFactory.setReadTimeout(15 * 600000);
httpClientFactory.setConnectionRequestTimeout(15 * 600000);
return httpClientFactory;
}
public ClientHttpRequestFactory createRequestFactory(){
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(400);
connectionManager.setDefaultMaxPerRoute(200);
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectionRequestTimeout(5000)
.setSocketTimeout(10000)
.build();
CloseableHttpClient httpClient = HttpClients
.custom()
.setConnectionManager(connectionManager)
.setKeepAliveStrategy(connectionKeepAliveStrategy())
.setDefaultRequestConfig(requestConfig)
.build();
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
private ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return (response,context)-> {
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if ( value != null && param.equalsIgnoreCase("timeout")){
try {
return Long.parseLong(value) * 999999999;
} catch (NumberFormatException exception) {
exception.printStackTrace();
}
}
}
return 15 * 60000;
} ;
}
#Bean(name = "webClient")
public WebClient webClient() {
TcpClient tcpClient = TcpClient
.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000 * 15)//15 minutes
.doOnConnected(connection -> {
connection.addHandlerLast(new ReadTimeoutHandler(15, TimeUnit.MINUTES));
connection.addHandlerLast(new WriteTimeoutHandler(15, TimeUnit.MINUTES));
});
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient).wiretap(true)))
.build();
}
None of this configs worked. I tried using okhttpclient like this.
public class OkHttpClientFactoryImpl implements OkHttpClientFactory {
#Override
public OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
ConnectionPool okHttpConnectionPool = new ConnectionPool(50, 30, TimeUnit.SECONDS);
builder.connectionPool(okHttpConnectionPool);
builder.connectTimeout(20, TimeUnit.MINUTES);
builder.readTimeout(20, TimeUnit.MINUTES);
builder.writeTimeout(20, TimeUnit.MINUTES);
builder.retryOnConnectionFailure(false);
return builder;
}
}
#Bean
#Qualifier("OKSpringCommonsRestTemplate")
public ClientHttpRequestFactory createOKCommonsRequestFactory() {
OkHttpClientFactoryImpl httpClientFactory = new OkHttpClientFactoryImpl();
OkHttpClient client = httpClientFactory.createBuilder(false).build();
return new OkHttp3ClientHttpRequestFactory(client);
}
It did not work. I don't know what could cause the connection to close other than the things i've setup.
After receiving an answer, i tried :
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofMinutes(10))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpClient client= HttpClient.newHttpClient();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
The result is the same. After 2 minutes i get an error saying java.io.IOException: HTTP/1.1 header parser received no bytes
I found the problem. It was in fact because of the server on the other end of the call. Express server is closing the connection, by default, after 2 minutes.
Increase Client Idle Timeout at server/LB to a higher number.

Access Https Rest Service using Spring RestTemplate

Can anybody provide me with a code sample to access the rest service URL secured with HTTPS using the Spring Rest template?
I have the certificate, username and password. Basic Authentication is used on the server-side and I want to create a client that can connect to that server using a provided certificate, username and password (if needed).
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File(keyStoreFile)),
keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build(),
NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(
socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
MyRecord record = restTemplate.getForObject(uri, MyRecord.class);
LOG.debug(record.toString());
Here is some code that will give you the general idea.
You need to create a custom ClientHttpRequestFactory in order to trust the certificate.
It looks like this:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
This is the implementation for MyCustomClientHttpRequestFactory:
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private final HostnameVerifier hostNameVerifier;
private final ServerInfo serverInfo;
public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
final ServerInfo serverInfo) {
this.hostNameVerifier = hostNameVerifier;
this.serverInfo = serverInfo;
}
#Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
.getSocketFactory());
}
super.prepareConnection(connection, httpMethod);
}
private SSLContext initSSLContext() {
try {
System.setProperty("https.protocols", "TLSv1");
// Set ssl trust manager. Verify against our server thumbprint
final SSLContext ctx = SSLContext.getInstance("TLSv1");
final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
final ThumbprintTrustManager thumbPrintTrustManager =
new ThumbprintTrustManager(null, verifier);
ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
return ctx;
} catch (final Exception ex) {
LOGGER.error(
"An exception was thrown while trying to initialize HTTP security manager.", ex);
return null;
}
}
In this case my serverInfo object contains the thumbprint of the server.
You need to implement the TrustManager interface to get
the SslThumbprintVerifier or any other method you want to verify your certificate (you can also decide to also always return true).
The value org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER allows all host names.
If you need to verify the host name,
you will need to implement it differently.
I'm not sure about the user and password and how you implemented it.
Often,
you need to add a header to the restTemplate named Authorization
with a value that looks like this: Base: <encoded user+password>.
The user+password must be Base64 encoded.
This is a solution with no deprecated class or method :
(Java 8 approved)
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
Important information : Using NoopHostnameVerifier is a security risk
One point from me. I used a mutual cert authentication with spring-boot microservices. The following is working for me, key points here are
keyManagerFactory.init(...) and sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) lines of code without them, at least for me, things did not work. Certificates are packaged by PKCS12.
#Value("${server.ssl.key-store-password}")
private String keyStorePassword;
#Value("${server.ssl.key-store-type}")
private String keyStoreType;
#Value("${server.ssl.key-store}")
private Resource resource;
private RestTemplate getRestTemplate() throws Exception {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
private HttpClient httpClient() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore trustStore = KeyStore.getInstance(keyStoreType);
if (resource.exists()) {
InputStream inputStream = resource.getInputStream();
try {
if (inputStream != null) {
trustStore.load(inputStream, keyStorePassword.toCharArray());
keyManagerFactory.init(trustStore, keyStorePassword.toCharArray());
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} else {
throw new RuntimeException("Cannot find resource: " + resource.getFilename());
}
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
}
Here is what I ended up with for the similar problem. The idea is the same as in #Avi's answer, but I also wanted to avoid the static "System.setProperty("https.protocols", "TLSv1");", so that any adjustments won't affect the system. Inspired by an answer from here http://www.coderanch.com/t/637177/Security/Disabling-handshake-message-Java
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = 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 = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier((hostname, session) -> true);
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] {"SSLv3"});
return socket;
}
}
}
You need to configure a raw HttpClient with SSL support, something like this:
#Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect()
throws ClientProtocolException, IOException {
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
ResponseEntity<String> response
= new RestTemplate(requestFactory).exchange(
urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
from: Baeldung

Resources