I'm trying to connect to a third party URL that is over HTTPS. They told us that all we need is to send them a HTTPS POST (no certificates/keystore required). However, I'm receiving the following exception:
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://mep.moxtra.com/sample/api/v1/auth/cus/token": sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:407)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:355)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:359)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:381)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
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)
... 33 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1496)
... 56 more
This is the code I have and I managed to implement some of the approaches to allow Spring RestTemplate make a POST to a HTTPS API.
RestTemplate restTemplate = new RestTemplate(buildCustomRequestFactory());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//This will only add the parameters I need on the body to call the POST operation
MultiValueMap<String, String> parametersMap = buildParametersMap(hubUserInfo);
HttpEntity<MultiValueMap<String, String>> jsonRequestEntity = new HttpEntity<MultiValueMap<String,String>>(parametersMap, requestHeaders);
try {
ResponseEntity<String> response = restTemplate.postForEntity(moxtraApiUrl, jsonRequestEntity, String.class);
System.out.println(response.getBody());
} catch (Exception e) {
e.printStackTrace();
}
For the creation of the CustomRequestFactory:
private HttpComponentsClientHttpRequestFactory buildCustomRequestFactory() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
requestFactory.setHttpClient(httpClient);
return requestFactory;
}
Do you know if I am missing some configuration or there's another approach to achieve making the POST request over HTTPS?
Thanks in advance.
I managed to solve the issue of the SSLHandshakeException by adding some code to the method: buildCustomRequestFactory
Before that, I needed to create a class called TrustAllStrategy
that implements the TrustStrategy
interface from org.apache.http.ssl
It was as simple as doing this:
public class TrustAllStrategy implements TrustStrategy {
/**
* Implement strategy to always trust certificates.
* @see {org.apache.http.ssl.TrustStrategy} TrustStrategy
*/
@Override
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}
Then I modified the method buildCustomRequestFactory
:
public static HttpComponentsClientHttpRequestFactory buildCustomRequestFactory(String host, int port) throws MoxtraSingleSignOnProcessException {
HttpComponentsClientHttpRequestFactory requestFactory = null;
SSLConnectionSocketFactory sslSocketFactory = null;
SSLContext sslContext = null;
HttpClient httpClient = null;
NoopHostnameVerifier hostNameVerifier = new NoopHostnameVerifier();
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustAllStrategy()).useProtocol(SecurityConstants.TLS_V_12).build();
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostNameVerifier);
clientBuilder.setSSLSocketFactory(sslSocketFactory);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register(SecurityConstants.HTTPS, sslSocketFactory)
.register(SecurityConstants.HTTP, PlainConnectionSocketFactory.getSocketFactory()).build();
PoolingHttpClientConnectionManager clientConnectionMgr = new PoolingHttpClientConnectionManager(registry);
HttpHost customHttpHost = new HttpHost(host, port);
clientConnectionMgr.setSocketConfig(customHttpHost, SocketConfig.custom().setSoTimeout(SOCKET_TIME_OUT).build());
httpClient = HttpClients.custom().setConnectionManager(clientConnectionMgr).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
} catch (Exception e) {
throw new MoxtraSingleSignOnProcessException("A problem occured when tried to setup SSL configuration for API call", e);
}
return requestFactory;
}
Host and port values are from the service provider. Then I created a context configured to use TLSv1.2 (since this is the one I was told to use by the provider).
After that I added some registry configurations for HTTPS and HTTP and just configured the connection manager object to have a Socket Time Out.
With that I was able to make a call to the service.
Also the Rest Template object creation changed to pass the parameters:
RestTemplate restTemplate = new RestTemplate(buildCustomRequestFactory(host, port));