Spring Boot WebCLient accept Self Signed Certificate, but not with InsecureTrustManagerFactory - spring-boot

I'm trying to build a REST client using Spring Boot and utilizing WebClient, however I'm conflicted when trying to config HTTPS call to a REST API.
When using RestTemplate, I was able to get self signed certificate working by using TrustSelfSignedStrategy(), thus even when the certificate is self signed, it is still being validated for its hostname, expiry date, etc.
In WebClient, so far I only found the way self signed certificate is by utilizing InsecureTrustManagerFactory, however this will cause the whole validation to be skipped as well, effectively void the purpose of using HTTPS in the first place.
As quoted from Netty documentations:
An insecure TrustManagerFactory that trusts all X.509 certificates without any verification.
NOTE: Never use this TrustManagerFactory in production. It is purely for testing purposes, and thus it is very insecure.
Is there any way I can use self signed certificate in WebClient without having to dismantle all the verification?

Yes, you can use a self signed certificate. What you need to do is add the self signed certificate into a java keystore and load it into your application by getting the keystore and transforming it into a TrustManager. Afterword you can supply the TrustManager to the SslContextBuilder which is needed for configuring the WebClient based on Netty. See below for an example:
Path truststorePath = Paths.get(/path/to/your/truststore)
InputStream truststoreInputStream = Files.newInputStream(truststorePath, StandardOpenOption.READ)
KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
truststore.load(truststoreInputStream, truststorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(truststore);
SslContext sslContext = SslContextBuilder.forClient()
.trustManager(trustManagerFactory)
.build()
HttpClient httpClient = HttpClient.create()
.secure(sslSpec -> sslSpec.sslContext(sslContext));
WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build()

Related

What type of certificate spring-boot application needs to have HTTPS connection?

I would like to make clients require HTTPS protocol to connect to my spring-boot backend application but I have no idea what kind of certificate I need for this.
Any certificate is good for this? Or I need to "turn on" some configurations?
Well, best you can do is to generate self-signed certificate.
There are many examples.
Also, you have to configure your backend.
Add dependency:
implementation 'org.apache.httpcomponents:httpclient:4.5'
Provide RestTemplate bean:
#Bean
private RestTemplate restTemplate() {
SSLContext sslContext = buildSslContext();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
private SSLContext buildSslContext() {
try {
char[] keyStorePassword = sslProperties.getKeyStorePassword();
return new SSLContextBuilder()
.loadKeyMaterial(
KeyStore.getInstance(new File(sslProperties.getKeyStore()), keyStorePassword),
keyStorePassword
).build();
} catch (Exception ex) {
throw new IllegalStateException("Unable to instantiate SSL context", ex);
} finally {
sslProperties.setKeyStorePassword(null);
sslProperties.setTrustStorePassword(null);
}
}
Provide required SSL properties in your application.properties or application.yaml file:
server:
ssl:
enabled: true
key-store: /path/to/key.keystore
key-store-password: password
key-alias: alias
trust-store: /path/to/truststore
trust-store-password: password
That's it. Now you can see your Tomcat is starting on 8080 (or another port) (https).
Alternatively, you can use my spring boot starter
1, You need a certificate for the application
there is two kind of certificates: self-signed certificate and ca-issued certificate
self-signed certificate
you can sign a certificate yourself use keytool
self-signed certificate has some disadvantage, browers will show a red alert page when access self-sign certificate site, and client need import the corresponding self-sign ca root certificate to communicate with server
ca-issued certificate
you can get a ca-issued certificate from let's encrypt
ca-issued certificate do not have all those disadvantage mentioned above ,it just work
2, You also need setting some spring boot configurations, see Spring Boot Docs - How to configure ssl

Certificate Based authentication, client share certificate to server

I have a requirement from a server application to share a SSL certificate. step i did:
I generated a self signed certificate against the Domain IP address(don't have domain name) where my application is deployed.
i shared the certificate to the server. they will keep the certificate in their trust store.
server is validating the request for the IP address. if the request are not coming from the IP address they are stopping them.
My question:
i have a spring boot application. do i need to make any change in my code for the certificate i have generated. if yes then what is the change.
Yes you need to make changes in your code. You need to load your keystore(with keypair) and if required also load your truststore into your http client. Most of the http clients require a SSLContext, so this would be sufficient for you:
KeyStore keyStore = ...;
TrustStore trustStore = ...;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
// Spring provides by default RestTemplate as HTTP Client, this client is an Apache HTTP Client wrapper
// The setup would be:
HttpClient httpClient = HttpClient.newBuilder();
.sslContext(sslFactory.getSslContext());
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory)

Does calling https service by Resttemplate work for all services whose certificate has been imported in the trustore of my client service?

I have a restemplate which can make call to multiple external systems over https.
I configured the resttemplate like so :
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial(key, keyPassword)
.build();
HttpClient client = HttpClients
.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
client);
httpComponentsClientHttpRequestFactory.setConnectTimeout(5000);
httpComponentsClientHttpRequestFactory.setReadTimeout(30000);
return new RestTemplate(httpComponentsClientHttpRequestFactory);
I have setup 2 mock services on 2 separate VMs with ssl enabled and I am testing this restemplate by calling those services over https. And it works.
What I want to confirm is that configuring the restemplate as shown in the above code and importing the certificates of the different services to be called in by the client truststore should work without any additional configurations right?
I am confused because in the code in this example here : https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose the author has used 2 different factories i.e. serverTomClientHttpRequestFactory & serverAliceClientHttpRequestFactory, however mine works with a single restemplate. can someone please shed some light on this topic?

Apache HttpClient for RestTemplate default behaviour if not setting up SSLSocketFactory and SSLContext

I am using Spring RestTemplate (config using HttpClient) to make a restful service call for a URL with https.
I explicitly setup SSL using following code:
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial().setProtocol().build
return new SSLConnectionSocketFactory(sslContext)
It works without any problem.
What would be the default behaviour if I don't setup SSLSocketFactory and SSLContext for HttpClient? Will it look for the cacerts under the installed JDK or it will disable the ssl?
I say Yes If your https website can be passed by your browser, or your certificate file is coming from the Certificate Authority such as GlobalSign, Verisign
we can see the SSLContext and SSLSocketFactory will be created automatically, so you can visit https without doing anything
if your certificate file is not trusted, and import to jdk cacerts still can't solve this, because HttpClient download the certificate file via SSLSocket, not from the jdk cacerts

How to trust ssl certificate programatically while using Spring Rest Client

I am using resttemplate from spring for consuming the Rest Json Webservice
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<Object> entity = new HttpEntity<Object>(glassRequest, headers);
postForObject = restTemplate.postForObject(url, entity, responseClass );
I am using above code for same. It works fine if there is no ssl certification at provider end. However , I get 403 if he uses certificate.
I know this can be handled by below two approches :-
1. Add certificate in trust store of application server.[But i am not running this code from application server but my junit test class]
2. Two add the certificate in java trust store. [However , i want to schedule this junit from enviroment where I don't have access ]
Is this can be done programmatically ?? Like i will include the certificates in my source code itself and then refer them by reading from classpath while making call to service.

Resources