{"id":614,"date":"2025-07-16T23:18:13","date_gmt":"2025-07-16T22:18:13","guid":{"rendered":"https:\/\/www.javaeinfacherkl\u00e4rt.de\/?p=614"},"modified":"2025-08-07T23:20:10","modified_gmt":"2025-08-07T22:20:10","slug":"sichere-rest-kommunikation-mit-mtls-in-spring-anwendungen","status":"publish","type":"post","link":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=614","title":{"rendered":"Sichere REST-Kommunikation mit mTLS in Spring-Anwendungen"},"content":{"rendered":"\n<p>In modernen verteilten Systemen ist die Absicherung der Kommunikation zwischen Diensten ein zentrales Anliegen. W\u00e4hrend HTTPS eine Einweg-Authentifizierung (Client vertraut dem Server) bietet, erm\u00f6glicht <strong>mutual TLS (mTLS)<\/strong> eine <strong>beidseitige Authentifizierung<\/strong>, bei der auch der <strong>Server den Client authentifiziert<\/strong>. Dies erh\u00f6ht die Sicherheit erheblich \u2013 besonders in Microservice-Architekturen oder bei hochsensiblen Anwendungen.<\/p>\n\n\n\n<p>Dieser Artikel zeigt, wie mTLS in einer <strong>Spring Boot<\/strong>-Anwendung f\u00fcr <strong>REST-Clients<\/strong> integriert wird, und erl\u00e4utert dabei die notwendigen Schritte, Konfigurationsoptionen und typische Stolperfallen.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Was ist mTLS?<\/h2>\n\n\n\n<p>Mutual TLS (mTLS) ist eine Erweiterung des TLS-Protokolls, bei der <strong>beide Kommunikationspartner<\/strong> ein X.509-Zertifikat vorweisen m\u00fcssen, um sich gegenseitig zu identifizieren. W\u00e4hrend klassisches TLS (wie bei HTTPS-Webseiten) nur das Server-Zertifikat pr\u00fcft, verlangt mTLS, dass auch der Client ein g\u00fcltiges Zertifikat vorweist.<\/p>\n\n\n\n<p><strong>Vorteile von mTLS:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Beidseitige Authentifizierung<\/li>\n\n\n\n<li>Keine Notwendigkeit f\u00fcr Benutzernamen\/Passw\u00f6rter<\/li>\n\n\n\n<li>Schutz gegen Man-in-the-Middle-Angriffe<\/li>\n\n\n\n<li>Integrit\u00e4t der \u00fcbermittelten Daten durch TLS<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Anwendungsfall<\/h2>\n\n\n\n<p>In diesem Beispiel betreiben wir eine <strong>Spring Boot REST-Client-Anwendung<\/strong>, die mit einem <strong>gesch\u00fctzten mTLS-gesicherten REST-Endpunkt<\/strong> kommunizieren m\u00f6chte. Die Gegenstelle verlangt, dass sich der Client mit einem Zertifikat authentifiziert.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Voraussetzungen<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Java 17 oder h\u00f6her<\/li>\n\n\n\n<li>Spring Boot (ab Version 2.7 oder 3.x)<\/li>\n\n\n\n<li>Eigene Zertifikate f\u00fcr Client und Server<\/li>\n\n\n\n<li>Zugriff auf den Serverzertifikat-Truststore<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Schritt 1: Erstellen der Zertifikate (f\u00fcr Testzwecke)<\/h2>\n\n\n\n<p>F\u00fcr Testzwecke k\u00f6nnen Zertifikate mit <strong>OpenSSL<\/strong> erstellt werden.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Root CA<\/span>\nopenssl req -x509 -<span class=\"hljs-keyword\">new<\/span> -nodes -keyout rootCA.key -out rootCA.crt -days <span class=\"hljs-number\">3650<\/span> -subj <span class=\"hljs-string\">\"\/CN=MyRootCA\"<\/span>\n\n<span class=\"hljs-comment\"># Server-Zertifikat<\/span>\nopenssl req -newkey rsa:<span class=\"hljs-number\">2048<\/span> -nodes -keyout server.key -out server.csr -subj <span class=\"hljs-string\">\"\/CN=localhost\"<\/span>\nopenssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -set_serial <span class=\"hljs-number\">01<\/span> -out server.crt -days <span class=\"hljs-number\">365<\/span>\n\n<span class=\"hljs-comment\"># Client-Zertifikat<\/span>\nopenssl req -newkey rsa:<span class=\"hljs-number\">2048<\/span> -nodes -keyout client.key -out client.csr -subj <span class=\"hljs-string\">\"\/CN=my-client\"<\/span>\nopenssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -set_serial <span class=\"hljs-number\">02<\/span> -out client.crt -days <span class=\"hljs-number\">365<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Wandle die Zertifikate in ein Java KeyStore-Format (.p12 oder .jks):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"># <span class=\"hljs-selector-tag\">Client<\/span> <span class=\"hljs-selector-tag\">keystore<\/span> (<span class=\"hljs-selector-tag\">enth<\/span>\u00e4<span class=\"hljs-selector-tag\">lt<\/span> <span class=\"hljs-selector-tag\">private<\/span> <span class=\"hljs-selector-tag\">key<\/span> + <span class=\"hljs-selector-tag\">client<\/span> <span class=\"hljs-selector-tag\">cert<\/span>)\n<span class=\"hljs-selector-tag\">openssl<\/span> <span class=\"hljs-selector-tag\">pkcs12<\/span> <span class=\"hljs-selector-tag\">-export<\/span> <span class=\"hljs-selector-tag\">-in<\/span> <span class=\"hljs-selector-tag\">client<\/span><span class=\"hljs-selector-class\">.crt<\/span> <span class=\"hljs-selector-tag\">-inkey<\/span> <span class=\"hljs-selector-tag\">client<\/span><span class=\"hljs-selector-class\">.key<\/span> <span class=\"hljs-selector-tag\">-out<\/span> <span class=\"hljs-selector-tag\">client-keystore<\/span><span class=\"hljs-selector-class\">.p12<\/span> <span class=\"hljs-selector-tag\">-name<\/span> <span class=\"hljs-selector-tag\">client<\/span> <span class=\"hljs-selector-tag\">-CAfile<\/span> <span class=\"hljs-selector-tag\">rootCA<\/span><span class=\"hljs-selector-class\">.crt<\/span> <span class=\"hljs-selector-tag\">-caname<\/span> <span class=\"hljs-selector-tag\">root<\/span> <span class=\"hljs-selector-tag\">-passout<\/span> <span class=\"hljs-selector-tag\">pass<\/span><span class=\"hljs-selector-pseudo\">:changeit<\/span>\n\n# <span class=\"hljs-selector-tag\">Truststore<\/span> (<span class=\"hljs-selector-tag\">mit<\/span> <span class=\"hljs-selector-tag\">Root-CA<\/span> <span class=\"hljs-selector-tag\">des<\/span> <span class=\"hljs-selector-tag\">Servers<\/span>)\n<span class=\"hljs-selector-tag\">keytool<\/span> <span class=\"hljs-selector-tag\">-importcert<\/span> <span class=\"hljs-selector-tag\">-file<\/span> <span class=\"hljs-selector-tag\">rootCA<\/span><span class=\"hljs-selector-class\">.crt<\/span> <span class=\"hljs-selector-tag\">-alias<\/span> <span class=\"hljs-selector-tag\">rootCA<\/span> <span class=\"hljs-selector-tag\">-keystore<\/span> <span class=\"hljs-selector-tag\">truststore<\/span><span class=\"hljs-selector-class\">.p12<\/span> <span class=\"hljs-selector-tag\">-storepass<\/span> <span class=\"hljs-selector-tag\">changeit<\/span> <span class=\"hljs-selector-tag\">-storetype<\/span> <span class=\"hljs-selector-tag\">PKCS12<\/span> <span class=\"hljs-selector-tag\">-noprompt<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Schritt 2: Konfiguration des REST-Clients<\/h2>\n\n\n\n<p>Spring Boot verwendet intern typischerweise den <code>RestTemplate<\/code> oder den moderneren <code>WebClient<\/code>. Beide k\u00f6nnen f\u00fcr mTLS konfiguriert werden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Beispiel mit <code>RestTemplate<\/code>:<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Bean\n<span class=\"hljs-keyword\">public<\/span> RestTemplate restTemplate() throws <span class=\"hljs-keyword\">Exception<\/span> {\n    char&#91;] keyStorePassword = <span class=\"hljs-string\">\"changeit\"<\/span>.toCharArray();\n\n    KeyStore keyStore = KeyStore.getInstance(<span class=\"hljs-string\">\"PKCS12\"<\/span>);\n    <span class=\"hljs-keyword\">try<\/span> (InputStream keyStoreStream = <span class=\"hljs-keyword\">new<\/span> FileInputStream(<span class=\"hljs-string\">\"client-keystore.p12\"<\/span>)) {\n        keyStore.load(keyStoreStream, keyStorePassword);\n    }\n\n    KeyStore trustStore = KeyStore.getInstance(<span class=\"hljs-string\">\"PKCS12\"<\/span>);\n    <span class=\"hljs-keyword\">try<\/span> (InputStream trustStoreStream = <span class=\"hljs-keyword\">new<\/span> FileInputStream(<span class=\"hljs-string\">\"truststore.p12\"<\/span>)) {\n        trustStore.load(trustStoreStream, keyStorePassword);\n    }\n\n    SSLContext sslContext = SSLContexts.custom()\n        .loadKeyMaterial(keyStore, keyStorePassword)\n        .loadTrustMaterial(trustStore, <span class=\"hljs-keyword\">null<\/span>)\n        .build();\n\n    HttpClient httpClient = HttpClients.custom()\n        .setSSLContext(sslContext)\n        .build();\n\n    HttpComponentsClientHttpRequestFactory factory = <span class=\"hljs-keyword\">new<\/span> HttpComponentsClientHttpRequestFactory(httpClient);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> RestTemplate(factory);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ud83d\udd10 Achte darauf, Zertifikat und Truststore niemals hart im Code zu hinterlegen. Nutze z.\u202fB. <code>application.properties<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Beispiel mit <code>WebClient<\/code>:<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Bean\n<span class=\"hljs-keyword\">public<\/span> WebClient webClient() throws <span class=\"hljs-keyword\">Exception<\/span> {\n    char&#91;] password = <span class=\"hljs-string\">\"changeit\"<\/span>.toCharArray();\n\n    KeyStore keyStore = KeyStore.getInstance(<span class=\"hljs-string\">\"PKCS12\"<\/span>);\n    keyStore.load(<span class=\"hljs-keyword\">new<\/span> FileInputStream(<span class=\"hljs-string\">\"client-keystore.p12\"<\/span>), password);\n\n    KeyStore trustStore = KeyStore.getInstance(<span class=\"hljs-string\">\"PKCS12\"<\/span>);\n    trustStore.load(<span class=\"hljs-keyword\">new<\/span> FileInputStream(<span class=\"hljs-string\">\"truststore.p12\"<\/span>), password);\n\n    SSLContext sslContext = SSLContexts.custom()\n        .loadKeyMaterial(keyStore, password)\n        .loadTrustMaterial(trustStore, <span class=\"hljs-keyword\">null<\/span>)\n        .build();\n\n    HttpClient httpClient = HttpClients.custom()\n        .setSSLContext(sslContext)\n        .build();\n\n    ClientHttpConnector connector = <span class=\"hljs-keyword\">new<\/span> HttpComponentsClientHttpConnector(httpClient);\n\n    <span class=\"hljs-keyword\">return<\/span> WebClient.builder()\n        .clientConnector(connector)\n        .build();\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Schritt 3: Nutzung des REST-Clients<\/h2>\n\n\n\n<p>Sobald der Client konfiguriert ist, kannst du ihn wie gewohnt verwenden:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@Autowired\nprivate RestTemplate restTemplate;\n\npublic <span class=\"hljs-keyword\">void<\/span> callSecureEndpoint() {\n    <span class=\"hljs-built_in\">String<\/span> url = <span class=\"hljs-string\">\"https:\/\/secure-api.internal.example.com\/data\"<\/span>;\n    ResponseEntity&lt;<span class=\"hljs-built_in\">String<\/span>&gt; response = restTemplate.getForEntity(url, <span class=\"hljs-built_in\">String<\/span>.class);\n    System.out.println(<span class=\"hljs-string\">\"Antwort: \"<\/span> + response.getBody());\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Tipps und Best Practices<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd04 Zertifikatrotation<\/h3>\n\n\n\n<p>Zertifikate sollten regelm\u00e4\u00dfig erneuert werden. Verwende m\u00f6glichst kurze G\u00fcltigkeitszeitr\u00e4ume und automatisierte Erneuerung z.\u202fB. via HashiCorp Vault oder cert-manager.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\uddea Testumgebung<\/h3>\n\n\n\n<p>F\u00fcr lokale Tests kann ein mTLS-Server mit Tools wie <a href=\"https:\/\/mitmproxy.org\/\">mitmproxy<\/a>, <a href=\"https:\/\/www.mock-server.com\/\">MockServer<\/a> oder selbstgebauter Spring Boot REST-API mit aktiviertem mTLS genutzt werden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\uddf0 Logging aktivieren<\/h3>\n\n\n\n<p>Bei Problemen mit TLS hilft das Aktivieren von Debug-Logs:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">-Djavax.net.debug=ssl:handshake\n<\/code><\/span><\/pre>\n\n\n<p>So kannst du genau sehen, welche Zertifikate verwendet werden, ob die Gegenstelle das Client-Zertifikat akzeptiert etc.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Fehlersuche (Troubleshooting)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate<\/code><\/h3>\n\n\n\n<p>\u2192 Der Server akzeptiert das Client-Zertifikat nicht. Stelle sicher, dass der Server die CA des Clients vertraut.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><code>SSLHandshakeException: PKIX path building failed<\/code><\/h3>\n\n\n\n<p>\u2192 Der Client kann dem Server-Zertifikat nicht vertrauen. \u00dcberpr\u00fcfe den Truststore und dessen Inhalte.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><code>KeyStoreException: problem accessing keystore<\/code><\/h3>\n\n\n\n<p>\u2192 Falsches Passwort oder besch\u00e4digte Datei. Stelle sicher, dass Pfad und Passwort korrekt sind.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p>Mutual TLS bietet eine starke, zertifikatsbasierte Authentifizierung zwischen REST-Diensten und ist besonders f\u00fcr hochsichere oder interne Service-Kommunikation sinnvoll. In Spring Boot ist die Integration f\u00fcr REST-Clients dank der flexiblen HTTP-Client-Infrastruktur relativ einfach umsetzbar \u2013 erfordert jedoch sorgf\u00e4ltiges Management von Zertifikaten und Keystores.<\/p>\n\n\n\n<p>Wer regelm\u00e4\u00dfig mit sensiblen Daten oder internen APIs arbeitet, sollte mTLS ernsthaft in Betracht ziehen \u2013 nicht nur als Sicherheitsma\u00dfnahme, sondern auch als professionellen Standard f\u00fcr Service-to-Service-Kommunikation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In modernen verteilten Systemen ist die Absicherung der Kommunikation zwischen Diensten ein zentrales Anliegen. W\u00e4hrend HTTPS eine Einweg-Authentifizierung (Client vertraut dem Server) bietet, erm\u00f6glicht mutual TLS (mTLS) eine beidseitige Authentifizierung, bei der auch der Server den Client authentifiziert. Dies erh\u00f6ht die Sicherheit erheblich \u2013 besonders in Microservice-Architekturen oder bei hochsensiblen Anwendungen. Dieser Artikel zeigt, wie [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-614","post","type-post","status-publish","format-standard","hentry","category-spring"],"_links":{"self":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/614","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=614"}],"version-history":[{"count":1,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/614\/revisions"}],"predecessor-version":[{"id":615,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/614\/revisions\/615"}],"wp:attachment":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=614"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=614"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=614"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}