In modernen verteilten Systemen ist die Absicherung der Kommunikation zwischen Diensten ein zentrales Anliegen. Während HTTPS eine Einweg-Authentifizierung (Client vertraut dem Server) bietet, ermöglicht mutual TLS (mTLS) eine beidseitige Authentifizierung, bei der auch der Server den Client authentifiziert. Dies erhöht die Sicherheit erheblich – besonders in Microservice-Architekturen oder bei hochsensiblen Anwendungen.

Dieser Artikel zeigt, wie mTLS in einer Spring Boot-Anwendung für REST-Clients integriert wird, und erläutert dabei die notwendigen Schritte, Konfigurationsoptionen und typische Stolperfallen.


Was ist mTLS?

Mutual TLS (mTLS) ist eine Erweiterung des TLS-Protokolls, bei der beide Kommunikationspartner ein X.509-Zertifikat vorweisen müssen, um sich gegenseitig zu identifizieren. Während klassisches TLS (wie bei HTTPS-Webseiten) nur das Server-Zertifikat prüft, verlangt mTLS, dass auch der Client ein gültiges Zertifikat vorweist.

Vorteile von mTLS:

  • Beidseitige Authentifizierung
  • Keine Notwendigkeit für Benutzernamen/Passwörter
  • Schutz gegen Man-in-the-Middle-Angriffe
  • Integrität der übermittelten Daten durch TLS

Anwendungsfall

In diesem Beispiel betreiben wir eine Spring Boot REST-Client-Anwendung, die mit einem geschützten mTLS-gesicherten REST-Endpunkt kommunizieren möchte. Die Gegenstelle verlangt, dass sich der Client mit einem Zertifikat authentifiziert.


Voraussetzungen

  • Java 17 oder höher
  • Spring Boot (ab Version 2.7 oder 3.x)
  • Eigene Zertifikate für Client und Server
  • Zugriff auf den Serverzertifikat-Truststore

Schritt 1: Erstellen der Zertifikate (für Testzwecke)

Für Testzwecke können Zertifikate mit OpenSSL erstellt werden.

# Root CA
openssl req -x509 -new -nodes -keyout rootCA.key -out rootCA.crt -days 3650 -subj "/CN=MyRootCA"

# Server-Zertifikat
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -set_serial 01 -out server.crt -days 365

# Client-Zertifikat
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=my-client"
openssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -set_serial 02 -out client.crt -days 365
Code-Sprache: PHP (php)

Wandle die Zertifikate in ein Java KeyStore-Format (.p12 oder .jks):

# Client keystore (enthält private key + client cert)
openssl pkcs12 -export -in client.crt -inkey client.key -out client-keystore.p12 -name client -CAfile rootCA.crt -caname root -passout pass:changeit

# Truststore (mit Root-CA des Servers)
keytool -importcert -file rootCA.crt -alias rootCA -keystore truststore.p12 -storepass changeit -storetype PKCS12 -noprompt
Code-Sprache: CSS (css)

Schritt 2: Konfiguration des REST-Clients

Spring Boot verwendet intern typischerweise den RestTemplate oder den moderneren WebClient. Beide können für mTLS konfiguriert werden.

Beispiel mit RestTemplate:

@Bean
public RestTemplate restTemplate() throws Exception {
    char[] keyStorePassword = "changeit".toCharArray();

    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    try (InputStream keyStoreStream = new FileInputStream("client-keystore.p12")) {
        keyStore.load(keyStoreStream, keyStorePassword);
    }

    KeyStore trustStore = KeyStore.getInstance("PKCS12");
    try (InputStream trustStoreStream = new FileInputStream("truststore.p12")) {
        trustStore.load(trustStoreStream, keyStorePassword);
    }

    SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, keyStorePassword)
        .loadTrustMaterial(trustStore, null)
        .build();

    HttpClient httpClient = HttpClients.custom()
        .setSSLContext(sslContext)
        .build();

    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);

    return new RestTemplate(factory);
}
Code-Sprache: PHP (php)

🔐 Achte darauf, Zertifikat und Truststore niemals hart im Code zu hinterlegen. Nutze z. B. application.properties.


Beispiel mit WebClient:

@Bean
public WebClient webClient() throws Exception {
    char[] password = "changeit".toCharArray();

    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new FileInputStream("client-keystore.p12"), password);

    KeyStore trustStore = KeyStore.getInstance("PKCS12");
    trustStore.load(new FileInputStream("truststore.p12"), password);

    SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, password)
        .loadTrustMaterial(trustStore, null)
        .build();

    HttpClient httpClient = HttpClients.custom()
        .setSSLContext(sslContext)
        .build();

    ClientHttpConnector connector = new HttpComponentsClientHttpConnector(httpClient);

    return WebClient.builder()
        .clientConnector(connector)
        .build();
}
Code-Sprache: PHP (php)

Schritt 3: Nutzung des REST-Clients

Sobald der Client konfiguriert ist, kannst du ihn wie gewohnt verwenden:

@Autowired
private RestTemplate restTemplate;

public void callSecureEndpoint() {
    String url = "https://secure-api.internal.example.com/data";
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    System.out.println("Antwort: " + response.getBody());
}
Code-Sprache: JavaScript (javascript)

Tipps und Best Practices

🔄 Zertifikatrotation

Zertifikate sollten regelmäßig erneuert werden. Verwende möglichst kurze Gültigkeitszeiträume und automatisierte Erneuerung z. B. via HashiCorp Vault oder cert-manager.

🧪 Testumgebung

Für lokale Tests kann ein mTLS-Server mit Tools wie mitmproxy, MockServer oder selbstgebauter Spring Boot REST-API mit aktiviertem mTLS genutzt werden.

🧰 Logging aktivieren

Bei Problemen mit TLS hilft das Aktivieren von Debug-Logs:

-Djavax.net.debug=ssl:handshake

So kannst du genau sehen, welche Zertifikate verwendet werden, ob die Gegenstelle das Client-Zertifikat akzeptiert etc.


Fehlersuche (Troubleshooting)

javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate

→ Der Server akzeptiert das Client-Zertifikat nicht. Stelle sicher, dass der Server die CA des Clients vertraut.


SSLHandshakeException: PKIX path building failed

→ Der Client kann dem Server-Zertifikat nicht vertrauen. Überprüfe den Truststore und dessen Inhalte.


KeyStoreException: problem accessing keystore

→ Falsches Passwort oder beschädigte Datei. Stelle sicher, dass Pfad und Passwort korrekt sind.


Fazit

Mutual TLS bietet eine starke, zertifikatsbasierte Authentifizierung zwischen REST-Diensten und ist besonders für hochsichere oder interne Service-Kommunikation sinnvoll. In Spring Boot ist die Integration für REST-Clients dank der flexiblen HTTP-Client-Infrastruktur relativ einfach umsetzbar – erfordert jedoch sorgfältiges Management von Zertifikaten und Keystores.

Wer regelmäßig mit sensiblen Daten oder internen APIs arbeitet, sollte mTLS ernsthaft in Betracht ziehen – nicht nur als Sicherheitsmaßnahme, sondern auch als professionellen Standard für Service-to-Service-Kommunikation.