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

Feature
One-Way TLS
Mutual TLS (mTLS)

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

Scenario
Why Custom SSL is Required

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 the RestTemplate 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).

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>

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