HTTP Clients with SSL
About
SSL (Secure Sockets Layer) or its newer version TLS (Transport Layer Security) is used when our application talks to another service over HTTPS. It ensures that:
The data sent is encrypted
The communication is private and secure
We are talking to a trusted server (by verifying its certificate)
This is essential when calling APIs or services that require HTTPS—especially in production environments or when dealing with sensitive data.
One-Way TLS vs Mutual TLS (mTLS)
One-Way TLS (Standard HTTPS)
Only the server presents a certificate to the client (usually our browser or app) to prove its identity.
How it works
Client sends a request.
Server replies with its SSL certificate.
Client checks if the server is trusted.
Once verified, communication is encrypted.
Use case Most websites, REST APIs, and apps use this. It ensures the client trusts the server but not the other way around.
Mutual TLS (mTLS)
Both the server and the client authenticate each other using certificates.
How it works
Client sends a request.
Server presents its certificate (as in one-way).
Client verifies server certificate.
Then client also sends its certificate to the server.
Server verifies the client’s identity.
If both are trusted, secure communication begins.
Use case Used in high-security systems, internal APIs, B2B systems, banking, or microservices communication where the server needs to trust only specific clients.
Comparison Table
Server Authentication
Yes
Yes
Client Authentication
No
Yes
Security Level
Good
Stronger
Typical Use
Public web, REST APIs
Internal APIs, Financial Systems, B2B
Client Certificate Needed
No
Yes
When We Need Custom SSL Setup ?
In most basic use cases, RestTemplate
works seamlessly with HTTPS endpoints by relying on the default JVM truststore, which contains standard, globally trusted certificate authorities (CAs). However, in enterprise environments or secure system architectures, this default behavior is insufficient or insecure. We often need a custom SSL configuration when connecting to internal services, non-public APIs, or systems with heightened security requirements.
Typical Scenarios That Require Custom SSL Configuration
Internal Microservices Use Self-Signed Certificates
The JVM does not trust self-signed certs by default. A custom truststore must be configured to explicitly trust them.
Our Organization Uses a Private Certificate Authority (CA)
Enterprises may issue certificates using an internal CA. These certs are not recognized by public trust chains and need to be manually added to a truststore.
Mutual TLS (mTLS) Is Required
When both client and server authenticate each other using certificates, the client needs to present its own certificate via a configured keystore.
We Need to Bypass Hostname Verification in Controlled Environments
In some non-prod or dynamic environments (e.g., container orchestration systems), DNS may not resolve expected hostnames. A relaxed hostname verifier is needed temporarily.
Strict Control Over TLS Versions and Cipher Suites
Legacy systems or regulated environments may require only specific TLS protocols or exclude vulnerable cipher suites. We will need a customized SSLContext
to enforce this.
External API Uses a Custom Root Certificate
Some fintech, banking, or regulated domains expose APIs with root certificates not included in the standard JVM truststore. These must be manually imported and trusted.
Automated Certificate Rotation
When using certs managed by tools like HashiCorp Vault or Let's Encrypt, our SSL config must dynamically reload trust/keystores or pull credentials at runtime.
Integration With HSM or External Keystores
When cryptographic material is managed by external providers (e.g., AWS KMS, Azure Key Vault), custom SSL code is needed to integrate with those sources.
Different Trust Requirements Per Environment
Dev and staging might use permissive or stubbed certs, while production requires strict validation using real certs—hence different truststore/keystore setup per profile.
1. Calling Internal APIs with Self-Signed Certs
Our team exposes a service internally (
https://internal-api.company.local
) using self-signed certs for development and staging. We must import these certs into a custom truststore and configure theRestTemplate
to use it.
2. Fintech App Using mTLS for Transaction APIs
A payment gateway requires both client and server certificates to be exchanged. We must configure a client keystore with our app's certificate and a truststore with the gateway’s certificate chain.
3. Zero Trust Networking
In a microservice mesh architecture (e.g., with Istio or Linkerd), each service is issued a short-lived certificate for mutual authentication. These certificates are rotated frequently, and the truststore needs to be dynamically managed.
4. Strict Regulatory Compliance
Healthcare applications complying with HIPAA or financial apps under PCI DSS must enforce TLS 1.2+, avoid weak ciphers, and sometimes restrict certificate validity periods—none of which can be handled by default SSL configurations.
SSL Configuration
Component
What It Is
Why It Matters / Meaning
Truststore
A file (usually .jks
or .p12
) containing certificates the client trusts
It holds the public keys or certificates of the servers we are willing to trust. Used to validate server certs.
Keystore
A file that holds the client’s own private key and certificate
Required for mutual TLS. It authenticates the client to the server.
Truststore Password
Password to access the truststore
Protects unauthorized access to the truststore.
Keystore Password
Password to access the keystore
Secures the client certificate and private key.
Key Alias
Label used to select a specific key pair in the keystore
Important if keystore has multiple keys.
SSLContext
Java object that manages SSL parameters and sockets
Central component used to configure custom SSL behavior in code.
X509TrustManager
Validates the server’s certificate
Often customized to accept specific CAs or bypass certain validations in dev/test environments.
HostnameVerifier
Validates that the server’s certificate matches the hostname in URL
Used to prevent man-in-the-middle attacks. Can be relaxed in some controlled cases.
Protocol (e.g., TLSv1.2)
Defines which SSL/TLS version to use
Ensures strong encryption. TLS 1.2+ is preferred; older versions are vulnerable.
Cipher Suites
Algorithms used in SSL handshakes and encryption
Can be customized to exclude weak or deprecated algorithms.
Custom SSLSocketFactory
Creates sockets using our custom SSLContext
Allows injection into HTTP clients for full control over SSL behavior.
PKCS12 / JKS Format
File formats used for keystores/truststores
PKCS12
is the modern standard; JKS
is Java’s legacy format. Both are supported by RestTemplate
.
Java System Properties
Global flags like javax.net.ssl.trustStore
Used to define SSL configuration at JVM level if not programmatically set.
Reloadable Configuration
Custom logic to reload certs/trust dynamically
Useful when certificates are rotated automatically (e.g., with Let's Encrypt or HashiCorp Vault).
We don’t always need both truststore and keystore. For one-way SSL (standard HTTPS), truststore is sufficient.
For two-way SSL (mTLS), both truststore and keystore are required.
Use programmatic configuration (via
SSLContext
orHttpComponentsClientHttpRequestFactory
) for fine-grained control.Avoid skipping validations unless it's strictly non-production and controlled.
How to Configure RestTemplate for SSL ?
Required Maven Dependencies
Add the following to our pom.xml
:
<dependencies>
<!-- Spring Boot Web Starter (includes RestTemplate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache HttpClient (to support SSL and advanced features) -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- Apache HttpClient fluent builder utilities (optional) -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
</dependencies>
If we are using Spring Boot 2.x, these will usually align properly. For Spring Boot 3.x, Apache HttpClient is still compatible but we may consider switching to Jakarta equivalents if required.
One-Way SSL Configuration for RestTemplate (Truststore Only)
This is when the client (our app) verifies the server's certificate, but the server doesn't validate the client's identity.
application.yml
# src/main/resources/application.yml
rest-client:
ssl:
trust-store: classpath:certs/truststore.jks
trust-store-password: changeit
Java Configuration
package com.example.config;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;
@Configuration
public class RestTemplateSSLConfig {
@Value("${rest-client.ssl.trust-store}")
private Resource trustStore;
@Value("${rest-client.ssl.trust-store-password}")
private String trustStorePassword;
@Bean
public RestTemplate restTemplate() throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
try (InputStream is = trustStore.getInputStream()) {
truststore.load(is, trustStorePassword.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(truststore, null)
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}
Mutual TLS (mTLS) Configuration for RestTemplate
This is when the client also presents its certificate to the server for authentication.
application.yml
# src/main/resources/application.yml
rest-client:
ssl:
trust-store: classpath:certs/truststore.jks
trust-store-password: changeit
key-store: classpath:certs/keystore.jks
key-store-password: changeit
Java Configuration
package com.example.config;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;
@Configuration
public class RestTemplateMutualTLSConfig {
@Value("${rest-client.ssl.trust-store}")
private Resource trustStore;
@Value("${rest-client.ssl.trust-store-password}")
private String trustStorePassword;
@Value("${rest-client.ssl.key-store}")
private Resource keyStore;
@Value("${rest-client.ssl.key-store-password}")
private String keyStorePassword;
@Bean
public RestTemplate restTemplate() throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
try (InputStream trustInput = trustStore.getInputStream()) {
truststore.load(trustInput, trustStorePassword.toCharArray());
}
KeyStore keystore = KeyStore.getInstance("JKS");
try (InputStream keyInput = keyStore.getInputStream()) {
keystore.load(keyInput, keyStorePassword.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(truststore, null)
.loadKeyMaterial(keystore, keyStorePassword.toCharArray())
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}
Directory Structure for Certs
Place our cert files under:
src/main/resources/certs/
└── keystore.jks
└── truststore.jks
Last updated