├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── LICENSE
├── README.md
├── client-bob
├── Dockerfile
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── de
│ │ │ └── jonashackt
│ │ │ ├── BobApplication.java
│ │ │ ├── client
│ │ │ ├── ServerClient.java
│ │ │ └── ServerClientImpl.java
│ │ │ ├── configuration
│ │ │ ├── OpenAPIConfig.java
│ │ │ └── RestClientCertConfiguration.java
│ │ │ └── controller
│ │ │ └── BobController.java
│ └── resources
│ │ ├── alice-keystore.p12
│ │ ├── application.yml
│ │ ├── client-truststore.jks
│ │ └── tom-keystore.p12
│ └── test
│ └── java
│ └── de
│ └── jonashackt
│ └── BobTest.java
├── client-truststore.png
├── docker-compose.yml
├── docker-network-client
├── pom.xml
└── src
│ └── test
│ └── java
│ └── de
│ └── jonashackt
│ └── ClientTest.java
├── pom.xml
├── renovate.json
├── server-alice
├── Dockerfile
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── de
│ │ │ └── jonashackt
│ │ │ ├── ServerAliceApplication.java
│ │ │ ├── configuration
│ │ │ └── WebSecurityConfig.java
│ │ │ └── controller
│ │ │ └── ServerAliceController.java
│ └── resources
│ │ ├── alice-csr.conf
│ │ ├── alice-keystore.jks
│ │ ├── alice-keystore.p12
│ │ ├── alice-truststore.jks
│ │ ├── alice.crt
│ │ ├── alice.csr
│ │ ├── aliceprivate.key
│ │ └── application.yml
│ └── test
│ ├── java
│ └── de
│ │ └── jonashackt
│ │ ├── RestClientCertTest.java
│ │ └── RestClientCertTestConfiguration.java
│ └── resources
│ ├── alice-keystore.p12
│ └── alice-truststore.jks
└── server-tom
├── Dockerfile
├── pom.xml
└── src
├── main
├── java
│ └── de
│ │ └── jonashackt
│ │ ├── ServerTomApplication.java
│ │ ├── configuration
│ │ └── WebSecurityConfig.java
│ │ └── controller
│ │ └── ServerTomController.java
└── resources
│ ├── application.yml
│ ├── tom-csr.conf
│ ├── tom-keystore.p12
│ ├── tom-truststore.jks
│ ├── tom.crt
│ ├── tom.csr
│ └── tomprivate.key
└── test
├── java
└── de
│ └── jonashackt
│ ├── RestClientCertTest.java
│ └── RestClientCertTestConfiguration.java
└── resources
├── tom-keystore.p12
└── tom-truststore.jks
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | name: github
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 |
10 | - uses: actions/checkout@v2
11 |
12 | - name: Set up JDK 15
13 | uses: actions/setup-java@v2
14 | with:
15 | distribution: 'adopt'
16 | java-version: 15
17 |
18 | - name: Build with Maven
19 | run: mvn -B install --no-transfer-progress --file pom.xml
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.springBeans
3 |
4 | # Package Files #
5 | *.jar
6 | *.war
7 | *.ear
8 |
9 | # Eclipse #
10 | .settings
11 | .project
12 | .classpath
13 | .studio
14 | target
15 |
16 | # Apple #
17 | .DS_Store
18 |
19 | # Intellij #
20 | .idea
21 | *.iml
22 | *.log
23 |
24 | # logback
25 | logback.out.xml
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jonas Hecht
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Multiple Spring Boot servers that are secured with different client certificates - called by RestTemplate
2 | =============================
3 | [](https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/actions)
4 | [](https://renovatebot.com)
5 |
6 | This repository basically forks all the ground work that was done in https://github.com/jonashackt/spring-boot-rest-clientcertificate. This is a basic example, where the client certificate secured server is a Spring Boot Application and the client is just a Testcase that uses Spring´s RestTemplate which is configured to use the client certificate.
7 |
8 | In contrast the present project focusses on the configuration of more than one client certificates and how to access REST endpoints from multiple servers that are secured by different client certificates with Spring´s RestTemplate.
9 |
10 | Therefore we use several Spring Boot based microservices that provide different client certificate secured REST endpoint and a separate microservice that accesses these services:
11 |
12 | ```
13 | -------------------------------------------
14 | | Docker Network scope __ |
15 | | ============|o¬| |
16 | | = ¯¯= |
17 | | = server-alice = |
18 | ============ | ============== ssl = = |
19 | = docker- = | = = -----> ================ |
20 | = network- = -----> = client-bob = __ |
21 | = client = | = __ __ = -----> ============|o¬| |
22 | ============ | ===|o¬|=|o¬|== ssl = ¯¯= |
23 | | ¯¯ ¯¯ = server-tom = |
24 | | = = |
25 | | ================ |
26 | -------------------------------------------
27 |
28 | ```
29 |
30 |
31 | For a general approach on how to generate private keys and certificates and create Java Keystores, have a look into https://github.com/jonashackt/spring-boot-rest-clientcertificate#generate-the-usual-key-and-crt---and-import-them-into-needed-keystore-jks-files
32 |
33 | # HowTo Use
34 |
35 | Everything you need to run a full build and __complete__ test (incl. Integrationtest of docker-network-client firing up all three microservices that´ll call each other with client certificate support) is this:
36 |
37 | ```
38 | mvn clean install
39 | ```
40 |
41 | Only, if you want to check manually, you can do a `docker-compose up -d` and open your Browser with [http:localhost:8080/swagger-ui.html] and fire up a GET-Request to /secretservers with Swagger :)
42 |
43 |
44 | # Integrationtesting with [testcontainers](https://www.testcontainers.org)
45 |
46 | As client-bob only has access to the DNS aliases `server-alice` and `server-tom`, if it itself is part of the Docker (Compose) network and these aliases are used to access both client certificate secured endpoints, we need another way to run an Integration test inside the Docker network scope.
47 |
48 | Therefore we use the [testcontainers](https://www.testcontainers.org) and the __docker-network-client__ that just calls __client-bob__ inside the Docker network.
49 |
50 | testcontainers could be []simply integrated by via Maven](https://www.testcontainers.org/usage.html#maven-dependencies):
51 |
52 | ```
53 |
54 | org.testcontainers
55 | testcontainers
56 | NewestTestcontainersVersionOnMavenCentral
57 | test
58 |
59 | ```
60 |
61 | And the code you need, to fire up all Docker Compose services / Docker Containers is really simple:
62 |
63 | ```
64 | package de.jonashackt;
65 |
66 | import org.apache.http.HttpStatus;
67 | import org.junit.ClassRule;
68 | import org.junit.Test;
69 | import org.junit.runner.RunWith;
70 | import org.springframework.test.context.ContextConfiguration;
71 | import org.springframework.test.context.junit4.SpringRunner;
72 | import org.testcontainers.containers.DockerComposeContainer;
73 | import org.testcontainers.containers.wait.strategy.Wait;
74 |
75 | import java.io.File;
76 |
77 | import static io.restassured.RestAssured.when;
78 | import static org.hamcrest.Matchers.containsString;
79 |
80 | @RunWith(SpringRunner.class)
81 | @ContextConfiguration()
82 | public class ClientTest {
83 |
84 | @ClassRule
85 | public static DockerComposeContainer services =
86 | new DockerComposeContainer(new File("../docker-compose.yml"))
87 | .withExposedService("server-alice", 8443,Wait.forListeningPort())
88 | .withExposedService("server-tom", 8443, Wait.forListeningPort())
89 | .withExposedService("client-bob", 8080, Wait.forHttp("/swagger-ui.html").forStatusCode(200)).withLocalCompose(true);
90 |
91 |
92 | @Test
93 | public void is_client_bob_able_to_call_all_servers_with_client_certs() {
94 |
95 | when()
96 | .get("http://localhost:8080/secretservers")
97 | .then()
98 | .statusCode(HttpStatus.SC_OK)
99 | .assertThat()
100 | .body(containsString("Both Servers called - Alice said 'Alice answering!' & Tom replied 'Tom answering!'."));
101 | }
102 | }
103 |
104 | ```
105 |
106 | # TlDR: How to create multiple keys & certificates for multiple servers - and add these into appropriate truststores / keystores
107 |
108 |
109 | ## server-alice keys and client certificate, truststore & keystore (see /server-alice/src/main/resources)
110 |
111 | #### 1. Private Key: aliceprivate.key
112 |
113 | ```
114 | openssl genrsa -des3 -out aliceprivate.key 1024
115 | ```
116 |
117 | - passphrase `alicepassword`
118 |
119 |
120 | #### 2. Certificate Signing Request (CSR): alice.csr
121 |
122 | ```
123 | openssl req -new -key aliceprivate.key -out alice.csr -config alice-csr.conf
124 | ```
125 |
126 | __Common Name__: `server-alice`, which will later be a DNS alias inside the Docker network
127 |
128 |
129 | #### 3. self-signed Certificate: alice.crt
130 |
131 | ```
132 | openssl x509 -req -days 3650 -in alice.csr -signkey aliceprivate.key -out alice.crt -extfile alice-csr.conf -extensions v3_req
133 | ```
134 |
135 |
136 | #### 4. Java Truststore Keystore, that inherits the generated self-signed Certificate: alice-truststore.jks
137 |
138 | ```
139 | keytool -import -file alice.crt -alias alicesCA -keystore alice-truststore.jks
140 | ```
141 |
142 | __the same password__ `alicepassword`
143 |
144 |
145 | #### 5. Java Keystore, that inherits Public and Private Keys (keypair): alice-keystore.jks
146 |
147 | ```
148 | openssl pkcs12 -export -in alice.crt -inkey aliceprivate.key -certfile alice.crt -name "alicecert" -out alice-keystore.p12
149 | ```
150 |
151 | __the same password__ `alicepassword`
152 |
153 | To read in KeyStore Explorer
154 |
155 | ```
156 | keytool -importkeystore -srckeystore alice-keystore.p12 -srcstoretype pkcs12 -destkeystore alice-keystore.jks -deststoretype JKS
157 | ```
158 |
159 |
160 |
161 | ## server-tom keys and client certificate, truststore & keystore (see /server-tom/src/main/resources)
162 |
163 | #### 1. Private Key: tomprivate.key
164 |
165 | ```
166 | openssl genrsa -des3 -out tomprivate.key 1024
167 | ```
168 |
169 | - passphrase `tompassword`
170 |
171 |
172 | #### 2. Certificate Signing Request (CSR): tom.csr
173 |
174 | ```
175 | openssl req -new -key tomprivate.key -out tom.csr -config tom-csr.conf
176 | ```
177 |
178 | __Common Name__: `server-tom`, which will later be a DNS alias inside the Docker network
179 |
180 |
181 | #### 3. self-signed Certificate: tom.crt
182 |
183 | ```
184 | openssl x509 -req -days 3650 -in tom.csr -signkey tomprivate.key -out tom.crt -extfile tom-csr.conf -extensions v3_req
185 | ```
186 |
187 |
188 | #### 4. Java Truststore Keystore, that inherits the generated self-signed Certificate: tom-truststore.jks
189 |
190 | ```
191 | keytool -import -file tom.crt -alias tomsCA -keystore tom-truststore.jks
192 | ```
193 |
194 | __the same password__ `tompassword`
195 |
196 |
197 | #### 5. Java Keystore, that inherits Public and Private Keys (keypair): tom-keystore.p12
198 |
199 | ```
200 | openssl pkcs12 -export -in tom.crt -inkey tomprivate.key -certfile tom.crt -name "tomcert" -out tom-keystore.p12
201 | ```
202 |
203 | __the same password__ `tompassword`
204 |
205 |
206 |
207 | ## client-bob truststore & keystore (see /server-alice/src/main/resources)
208 |
209 | #### 1. Java Truststore Keystore, that inherits the generated self-signed Certificate: client-truststore.jks
210 |
211 | ```
212 | keytool -import -file alice.crt -alias alicesCA -keystore client-truststore.jks
213 | keytool -import -file tom.crt -alias tomsCA -keystore client-truststore.jks
214 | ```
215 |
216 | __password__ `bobpassword`
217 |
218 | In KeyStore Explorer this should look like this:
219 |
220 | 
221 |
222 |
223 | #### 2. Java Keystores, that inherit Public and Private Keys (keypair): copy alice-keystore.p12 & tom-keystore.p12
224 |
225 | As Apache HttpClient isn´t able to handle [more than one client certificate for the same SSLContext](http://mail-archives.apache.org/mod_mbox/hc-httpclient-users/201109.mbox/%3C1315998630.3176.17.camel@ubuntu%3E), we need to provide two of them. Therefore we don´t need to add two private keys and certificates to one Keystore - we can just use both Keystores we already assembled before. So we copy `alice-keystore.p12` & `tom-keystore.p12` to clien-bob/src/main/resources and use them in the [RestClientCertConfiguration](https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/blob/master/client-bob/src/main/java/de/jonashackt/configuration/RestClientCertConfiguration.java) like this:
226 |
227 | ```
228 | import org.apache.commons.io.FileUtils;
229 | import org.apache.http.client.HttpClient;
230 | import org.apache.http.impl.client.HttpClients;
231 | import org.apache.http.ssl.SSLContextBuilder;
232 | import org.springframework.beans.factory.annotation.Value;
233 | import org.springframework.context.annotation.Bean;
234 | import org.springframework.context.annotation.Configuration;
235 | import org.springframework.core.io.Resource;
236 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
237 |
238 | import javax.net.ssl.SSLContext;
239 | import java.io.File;
240 | import java.io.IOException;
241 |
242 | @Configuration
243 | public class RestClientCertConfiguration {
244 |
245 | private char[] bobPassword = "bobpassword".toCharArray();
246 | private char[] tomPassword = "tompassword".toCharArray();
247 |
248 | @Value("classpath:alice-keystore.p12")
249 | private Resource aliceKeystoreResource;
250 |
251 | @Value("classpath:tom-keystore.p12")
252 | private Resource tomKeystoreResource;
253 |
254 | @Value("classpath:client-truststore.jks")
255 | private Resource truststoreResource;
256 | private char[] alicePassword = "alicepassword".toCharArray();
257 |
258 | @Bean
259 | public HttpComponentsClientHttpRequestFactory serverTomClientHttpRequestFactory() throws Exception {
260 | SSLContext sslContext = SSLContextBuilder
261 | .create()
262 | .loadKeyMaterial(inStream2File(tomKeystoreResource), tomPassword, tomPassword)
263 | .loadTrustMaterial(inStream2File(truststoreResource), bobPassword)
264 | .build();
265 |
266 | HttpClient client = HttpClients.custom()
267 | .setSSLContext(sslContext)
268 | .build();
269 |
270 | return new HttpComponentsClientHttpRequestFactory(client);
271 | }
272 |
273 | @Bean
274 | public HttpComponentsClientHttpRequestFactory serverAliceClientHttpRequestFactory() throws Exception {
275 | SSLContext sslContext = SSLContextBuilder
276 | .create()
277 | .loadKeyMaterial(inStream2File(aliceKeystoreResource), alicePassword, alicePassword)
278 | .loadTrustMaterial(inStream2File(truststoreResource), bobPassword)
279 | .build();
280 |
281 | HttpClient client = HttpClients.custom()
282 | .setSSLContext(sslContext)
283 | .build();
284 |
285 | return new HttpComponentsClientHttpRequestFactory(client);
286 | }
287 |
288 | private File inStream2File(Resource resource) {
289 | try {
290 | File tempFile = File.createTempFile("file", ".tmp");
291 | FileUtils.copyInputStreamToFile(resource.getInputStream(), tempFile);
292 | return tempFile;
293 | } catch (IOException e) {
294 | throw new RuntimeException("Problems loading Keystores", e);
295 | }
296 | }
297 | }
298 | ```
299 |
300 | Now we´re able to insert individual SSLContexts into Spring´s RestTemplate. Therefore see [ServerClientImpl](https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/blob/master/client-bob/src/main/java/de/jonashackt/client/ServerClientImpl.java):
301 |
302 | ```
303 | import org.springframework.beans.factory.annotation.Autowired;
304 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
305 | import org.springframework.stereotype.Component;
306 | import org.springframework.web.client.RestTemplate;
307 |
308 | @Component
309 | public class ServerClientImpl implements ServerClient {
310 |
311 | @Autowired
312 | private HttpComponentsClientHttpRequestFactory serverAliceClientHttpRequestFactory;
313 |
314 | @Autowired
315 | private HttpComponentsClientHttpRequestFactory serverTomClientHttpRequestFactory;
316 |
317 | private RestTemplate restTemplate = new RestTemplate();
318 |
319 | @Override
320 | public String callServerAlice() {
321 | restTemplate.setRequestFactory(serverAliceClientHttpRequestFactory);
322 |
323 | return restTemplate.getForObject("https://server-alice:8443/hello", String.class);
324 | }
325 |
326 | @Override
327 | public String callServerTom() {
328 | restTemplate.setRequestFactory(serverTomClientHttpRequestFactory);
329 |
330 | return restTemplate.getForObject("https://server-tom:8443/hello", String.class);
331 | }
332 | }
333 | ```
334 |
335 |
336 |
337 | # Links
338 |
339 | https://stackoverflow.com/questions/25869428/classpath-resource-not-found-when-running-as-jar
340 |
341 | https://www.thomas-krenn.com/de/wiki/Openssl_Multi-Domain_CSR_erstellen
342 |
343 | https://stackoverflow.com/questions/30977264/subject-alternative-name-not-present-in-certificate
344 |
345 | https://stackoverflow.com/questions/21488845/how-can-i-generate-a-self-signed-certificate-with-subjectaltname-using-openssl
346 |
347 | --> this is not the only solution, see `-extfile` and `-extensions` CLI paramters!
348 |
349 | https://serverfault.com/questions/779475/openssl-add-subject-alternate-name-san-when-signing-with-ca
350 |
351 | #### Multiple certificates handling in Java Keystores:
352 |
353 | Look into the documentation of Tomcat in section `keyAlias`: http://tomcat.apache.org/tomcat-6.0-doc/config/http.html#SSL_Support
354 |
355 | https://stackoverflow.com/questions/5292074/how-to-specify-outbound-certificate-alias-for-https-calls
356 |
357 | https://stackoverflow.com/questions/6370745/can-we-load-multiple-certificates-keys-in-a-key-store
358 |
--------------------------------------------------------------------------------
/client-bob/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-alpine
2 |
3 | MAINTAINER Jonas Hecht
4 |
5 | VOLUME /tmp
6 |
7 | # Add Spring Boot app.jar to Container
8 | ADD "target/client-bob-0.0.1-SNAPSHOT.jar" app.jar
9 |
10 | ENV JAVA_OPTS=""
11 |
12 | # Fire up our Spring Boot app by default
13 | ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
--------------------------------------------------------------------------------
/client-bob/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | client-bob
7 | 0.0.1-SNAPSHOT
8 | jar
9 |
10 |
11 | de.jonashackt
12 | spring-boot-rest-clientcertificates-docker-compose
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 | UTF-8
18 | 1.8
19 | 1.8.0
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 |
29 |
30 | org.apache.httpcomponents
31 | httpclient
32 |
33 |
34 |
35 | org.springdoc
36 | springdoc-openapi-ui
37 | ${springdoc-openapi-ui.version}
38 |
39 |
40 |
41 |
42 | commons-io
43 | commons-io
44 | 2.19.0
45 |
46 |
47 |
48 |
49 | org.springframework.boot
50 | spring-boot-starter-test
51 | test
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-maven-plugin
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/BobApplication.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class BobApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(BobApplication.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/client/ServerClient.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.client;
2 |
3 | public interface ServerClient {
4 | String callServerAlice();
5 |
6 | String callServerTom();
7 | }
8 |
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/client/ServerClientImpl.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.client;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.impl.client.HttpClients;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
7 | import org.springframework.stereotype.Component;
8 | import org.springframework.web.client.RestTemplate;
9 |
10 | import javax.net.ssl.SSLContext;
11 |
12 | @Component
13 | public class ServerClientImpl implements ServerClient {
14 |
15 | @Autowired
16 | private SSLContext serverTomSSLContext;
17 |
18 | @Autowired
19 | private SSLContext serverAliceSSLContext;
20 |
21 | private RestTemplate restTemplate = new RestTemplate();
22 |
23 | @Override
24 | public String callServerAlice() {
25 | restTemplate.setRequestFactory(createHttpComponentsClientHttpRequestFactory(serverAliceSSLContext));
26 |
27 | return restTemplate.getForObject("https://server-alice:8443/hello", String.class);
28 | }
29 |
30 | @Override
31 | public String callServerTom() {
32 | restTemplate.setRequestFactory(createHttpComponentsClientHttpRequestFactory(serverTomSSLContext));
33 |
34 | return restTemplate.getForObject("https://server-tom:8443/hello", String.class);
35 | }
36 |
37 | private HttpComponentsClientHttpRequestFactory createHttpComponentsClientHttpRequestFactory(SSLContext sslContext) {
38 | HttpClient client = HttpClients.custom()
39 | .setSSLContext(sslContext)
40 | .build();
41 |
42 | return new HttpComponentsClientHttpRequestFactory(client);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/configuration/OpenAPIConfig.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.configuration;
2 |
3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition;
4 | import io.swagger.v3.oas.annotations.info.Info;
5 | import io.swagger.v3.oas.annotations.info.License;
6 | import io.swagger.v3.oas.annotations.servers.Server;
7 |
8 | @OpenAPIDefinition(
9 | info = @Info(
10 | title = "REST Client uses clientcertificate to authenticate to Spring Boot Server",
11 | version = "2.0",
12 | description = "See https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose for more information",
13 | license = @License(
14 | name = "Apache License Version 2.0",
15 | url = "https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/blob/master/LICENSE"
16 | )
17 | ),
18 | servers = @Server(url = "http://client-bob:8080")
19 | )
20 | public class OpenAPIConfig {
21 | }
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/configuration/RestClientCertConfiguration.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.configuration;
2 |
3 | import org.apache.commons.io.FileUtils;
4 | import org.apache.http.ssl.SSLContextBuilder;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.core.io.Resource;
9 |
10 | import javax.net.ssl.SSLContext;
11 | import java.io.File;
12 | import java.io.IOException;
13 |
14 | @Configuration
15 | public class RestClientCertConfiguration {
16 |
17 | private char[] bobPassword = "bobpassword".toCharArray();
18 | private char[] tomPassword = "tompassword".toCharArray();
19 | private char[] alicePassword = "alicepassword".toCharArray();
20 |
21 | @Value("classpath:alice-keystore.p12")
22 | private Resource aliceKeystoreResource;
23 |
24 | @Value("classpath:tom-keystore.p12")
25 | private Resource tomKeystoreResource;
26 |
27 | @Value("classpath:client-truststore.jks")
28 | private Resource truststoreResource;
29 |
30 | @Bean
31 | public SSLContext serverTomSSLContext() throws Exception {
32 | return SSLContextBuilder
33 | .create()
34 | .loadKeyMaterial(inStream2File(tomKeystoreResource), tomPassword, tomPassword)
35 | .loadTrustMaterial(inStream2File(truststoreResource), bobPassword)
36 | .build();
37 | }
38 |
39 | @Bean
40 | public SSLContext serverAliceSSLContext() throws Exception {
41 | return SSLContextBuilder
42 | .create()
43 | .loadKeyMaterial(inStream2File(aliceKeystoreResource), alicePassword, alicePassword)
44 | .loadTrustMaterial(inStream2File(truststoreResource), bobPassword)
45 | .build();
46 | }
47 |
48 |
49 | /**
50 | * The ugly need to generate a File from a InputStream - because SSLContextBuild.loadKeyMaterial only accepts File,
51 | * but retrieving Files from within Spring Boot Fatjars is only possible through Resources ->
52 | * see https://stackoverflow.com/questions/25869428/classpath-resource-not-found-when-running-as-jar
53 | */
54 | private File inStream2File(Resource resource) {
55 | try {
56 | File tempFile = File.createTempFile("file", ".tmp");
57 | tempFile.deleteOnExit();
58 | FileUtils.copyInputStreamToFile(resource.getInputStream(), tempFile);
59 | return tempFile;
60 | } catch (IOException e) {
61 | throw new RuntimeException("Problems loading Keystores", e);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/client-bob/src/main/java/de/jonashackt/controller/BobController.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.controller;
2 |
3 | import de.jonashackt.client.ServerClient;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RequestMethod;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | @RestController
10 | public class BobController {
11 |
12 | @Autowired
13 | private ServerClient serverClient;
14 |
15 | @RequestMapping(path="/secretservers", method=RequestMethod.GET)
16 | public String servercalls() {
17 | System.out.println("Calling Alice secretely!");
18 | String serverAliceResponse = serverClient.callServerAlice();
19 |
20 | System.out.println("Calling Tom secretely!");
21 | String serverTomResponse = serverClient.callServerTom();
22 |
23 | return String.format("Both Servers called - Alice said '%s' & Tom replied '%s'.", serverAliceResponse, serverTomResponse);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/client-bob/src/main/resources/alice-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/client-bob/src/main/resources/alice-keystore.p12
--------------------------------------------------------------------------------
/client-bob/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
--------------------------------------------------------------------------------
/client-bob/src/main/resources/client-truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/client-bob/src/main/resources/client-truststore.jks
--------------------------------------------------------------------------------
/client-bob/src/main/resources/tom-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/client-bob/src/main/resources/tom-keystore.p12
--------------------------------------------------------------------------------
/client-bob/src/test/java/de/jonashackt/BobTest.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.junit.jupiter.api.Disabled;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.boot.web.server.LocalServerPort;
7 | import org.springframework.web.client.RestTemplate;
8 |
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.containsString;
11 |
12 | @SpringBootTest(
13 | classes = BobApplication.class,
14 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
15 | )
16 | public class BobTest {
17 |
18 | @LocalServerPort
19 | private int port;
20 |
21 | private RestTemplate restTemplate = new RestTemplate();
22 |
23 | @Disabled("no localhost support, because without Docker we would need to bind the same Port 8443 of server-alice and server-tom two times, which isn´t possible")
24 | @Test
25 | public void is_hello_resource_callable_with_client_cert() {
26 | String response = restTemplate.getForObject("https://localhost:" + port + "/secretservers", String.class);
27 |
28 | assertThat(response, containsString("Both Servers called - Alice said 'Alice answering!' & Tom replied 'Tom answering!'."));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/client-truststore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/client-truststore.png
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 |
5 | server-alice:
6 | build: ./server-alice
7 | ports:
8 | - "8443"
9 | tty:
10 | true
11 | restart:
12 | unless-stopped
13 |
14 | server-tom:
15 | build: ./server-tom
16 | ports:
17 | - "8443"
18 | tty:
19 | true
20 | restart:
21 | unless-stopped
22 |
23 | client-bob:
24 | build: ./client-bob
25 | ports:
26 | - "8080:8080"
27 | tty:
28 | true
29 | restart:
30 | unless-stopped
--------------------------------------------------------------------------------
/docker-network-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | docker-network-client
7 | 0.0.1-SNAPSHOT
8 | jar
9 |
10 |
11 | de.jonashackt
12 | spring-boot-rest-clientcertificates-docker-compose
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 | UTF-8
18 | 1.8
19 | 4.5.1
20 | 1.21.1
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-web
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-test
33 | test
34 |
35 |
36 |
37 | io.rest-assured
38 | rest-assured
39 | ${rest-assured.version}
40 | test
41 |
42 |
43 |
44 | org.testcontainers
45 | testcontainers
46 | ${testcontainers.version}
47 | test
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/docker-network-client/src/test/java/de/jonashackt/ClientTest.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.apache.http.HttpStatus;
4 | import org.junit.ClassRule;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.test.context.ContextConfiguration;
8 | import org.springframework.test.context.junit4.SpringRunner;
9 | import org.testcontainers.containers.DockerComposeContainer;
10 | import org.testcontainers.containers.wait.strategy.Wait;
11 |
12 | import java.io.File;
13 |
14 | import static io.restassured.RestAssured.when;
15 | import static org.hamcrest.Matchers.containsString;
16 |
17 | @RunWith(SpringRunner.class)
18 | @ContextConfiguration()
19 | public class ClientTest {
20 |
21 | @ClassRule
22 | public static DockerComposeContainer services =
23 | new DockerComposeContainer(new File("../docker-compose.yml"))
24 | .withExposedService("server-alice", 8443,Wait.forListeningPort())
25 | .withExposedService("server-tom", 8443, Wait.forListeningPort())
26 | .withExposedService("client-bob", 8080, Wait.forHttp("/swagger-ui.html").forStatusCode(200));
27 |
28 | @Test
29 | public void is_client_bob_able_to_call_all_servers_with_client_certs() {
30 |
31 | when()
32 | .get("http://localhost:8080/secretservers")
33 | .then()
34 | .statusCode(HttpStatus.SC_OK)
35 | .assertThat()
36 | .body(containsString("Both Servers called - Alice said 'Alice answering!' & Tom replied 'Tom answering!'."));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | de.jonashackt
7 | spring-boot-rest-clientcertificates-docker-compose
8 | 0.0.1-SNAPSHOT
9 | pom
10 |
11 | Example project showing how to access REST endpoints from multiple servers that are secured by different client certificates with Spring´s RestTemplate
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-parent
16 | 2.7.18
17 |
18 |
19 |
20 |
21 | ${project.basedir}
22 |
23 |
24 |
25 | server-alice
26 | server-tom
27 | client-bob
28 | docker-network-client
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true,
6 | "automergeType": "branch",
7 | "major": {
8 | "automerge": false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/server-alice/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-alpine
2 |
3 | MAINTAINER Jonas Hecht
4 |
5 | VOLUME /tmp
6 |
7 | # Add Spring Boot app.jar to Container
8 | ADD "target/server-alice-0.0.1-SNAPSHOT.jar" app.jar
9 |
10 | ENV JAVA_OPTS=""
11 |
12 | # Fire up our Spring Boot app by default
13 | ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
--------------------------------------------------------------------------------
/server-alice/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | server-alice
7 | 0.0.1-SNAPSHOT
8 | jar
9 |
10 |
11 | de.jonashackt
12 | spring-boot-rest-clientcertificates-docker-compose
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 | UTF-8
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-web
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-security
31 |
32 |
33 |
34 |
35 | org.apache.httpcomponents
36 | httpclient
37 |
38 |
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-test
43 | test
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-maven-plugin
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/server-alice/src/main/java/de/jonashackt/ServerAliceApplication.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class ServerAliceApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(ServerAliceApplication.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/server-alice/src/main/java/de/jonashackt/configuration/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.configuration;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
7 |
8 | @Configuration
9 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
10 |
11 | /*
12 | * Enable x509 client authentication.
13 | */
14 | @Override
15 | protected void configure(HttpSecurity http) throws Exception {
16 | http.x509();
17 | }
18 |
19 | /*
20 | * Create an in-memory authentication manager. We create 1 user (localhost which
21 | * is the CN of the client certificate) which has a role of USER.
22 | */
23 | @Override
24 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
25 | auth.inMemoryAuthentication()
26 | .withUser("localhost")
27 | .password("none")
28 | .roles("USER");
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server-alice/src/main/java/de/jonashackt/controller/ServerAliceController.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.controller;
2 |
3 | import org.springframework.web.bind.annotation.RequestMapping;
4 | import org.springframework.web.bind.annotation.RequestMethod;
5 | import org.springframework.web.bind.annotation.RestController;
6 |
7 | @RestController
8 | public class ServerAliceController {
9 |
10 | public static final String RESPONSE = "Alice answering!";
11 |
12 | @RequestMapping(path="/hello", method=RequestMethod.GET)
13 | public String helloWorld() {
14 | System.out.println("Alice´ Server was called");
15 | return RESPONSE;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice-csr.conf:
--------------------------------------------------------------------------------
1 | [req]
2 | distinguished_name = req_distinguished_name
3 | req_extensions = v3_req
4 | prompt = no
5 |
6 | [req_distinguished_name]
7 | C = DE
8 | ST = Thuringia
9 | L = Erfurt
10 | O = Alice Corp
11 | OU = Team Foo
12 | CN = server-alice
13 |
14 | [v3_req]
15 | keyUsage = keyEncipherment, dataEncipherment
16 | extendedKeyUsage = serverAuth
17 | subjectAltName = @alt_names
18 | [alt_names]
19 | DNS.1 = server-alice
20 | DNS.2 = localhost
21 |
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice-keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-alice/src/main/resources/alice-keystore.jks
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-alice/src/main/resources/alice-keystore.p12
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice-truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-alice/src/main/resources/alice-truststore.jks
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICqDCCAhGgAwIBAgIJAJfsRKTuMVkVMA0GCSqGSIb3DQEBBQUAMHExCzAJBgNV
3 | BAYTAkRFMRIwEAYDVQQIDAlUaHVyaW5naWExDzANBgNVBAcMBkVyZnVydDETMBEG
4 | A1UECgwKQWxpY2UgQ29ycDERMA8GA1UECwwIVGVhbSBGb28xFTATBgNVBAMMDHNl
5 | cnZlci1hbGljZTAeFw0xNzEyMTIxODEwMDhaFw0yNzEyMTAxODEwMDhaMHExCzAJ
6 | BgNVBAYTAkRFMRIwEAYDVQQIDAlUaHVyaW5naWExDzANBgNVBAcMBkVyZnVydDET
7 | MBEGA1UECgwKQWxpY2UgQ29ycDERMA8GA1UECwwIVGVhbSBGb28xFTATBgNVBAMM
8 | DHNlcnZlci1hbGljZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7WKw0oR+
9 | WPiHfwUSm7OLyMKbdc1c08x47mrtJagHKhOmmTCLlZOZ6L1XzidgnF8dvG+7mODn
10 | oUjMDWAAmR65WLOV3KM5sZAnPmIK9/KzvO837tixEbrueQAC5IOYmdo9oc9rr5Vz
11 | iWzAa3xUtloEl2Jme1hdsq1AUtX35u0Ap3ECAwEAAaNIMEYwCwYDVR0PBAQDAgQw
12 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMCIGA1UdEQQbMBmCDHNlcnZlci1hbGljZYIJ
13 | bG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBALyWWCPUJAwapV4g3qJ0PwATEkkd
14 | gC/nzm3J8KSsBBVAnzAn/IrWV1O3f+FDaxOMUyOXbYDql96okpKeohXjT/8Q1V+F
15 | iWWsBDvVS5q8OPlx0fdfk8bnEqUUa7T17WQLnfTl42f38pp/9p0sdIB3MVp2tRJZ
16 | p27l9wAt3E2dJDr1
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server-alice/src/main/resources/alice.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIICCDCCAXECAQAwcTELMAkGA1UEBhMCREUxEjAQBgNVBAgMCVRodXJpbmdpYTEP
3 | MA0GA1UEBwwGRXJmdXJ0MRMwEQYDVQQKDApBbGljZSBDb3JwMREwDwYDVQQLDAhU
4 | ZWFtIEZvbzEVMBMGA1UEAwwMc2VydmVyLWFsaWNlMIGfMA0GCSqGSIb3DQEBAQUA
5 | A4GNADCBiQKBgQDtYrDShH5Y+Id/BRKbs4vIwpt1zVzTzHjuau0lqAcqE6aZMIuV
6 | k5novVfOJ2CcXx28b7uY4OehSMwNYACZHrlYs5XcozmxkCc+Ygr38rO87zfu2LER
7 | uu55AALkg5iZ2j2hz2uvlXOJbMBrfFS2WgSXYmZ7WF2yrUBS1ffm7QCncQIDAQAB
8 | oFcwVQYJKoZIhvcNAQkOMUgwRjALBgNVHQ8EBAMCBDAwEwYDVR0lBAwwCgYIKwYB
9 | BQUHAwEwIgYDVR0RBBswGYIMc2VydmVyLWFsaWNlgglsb2NhbGhvc3QwDQYJKoZI
10 | hvcNAQELBQADgYEAb4ZuIj5jgAtryg2CQ1A6jAvkq96WolStj+iW7SvekgKZRNEJ
11 | CjvYrXD+0ysGu2VKCksCAAFNy/gQUWCBCvRZJ8VYD/m8ydFcsLDpZ/IfZ7OS5hy2
12 | A8KKmmcrDSTZHMCdt+7rmr+bVpjiy7iVakXxiwo/YoERTPtQcfXEliiHgIM=
13 | -----END CERTIFICATE REQUEST-----
14 |
--------------------------------------------------------------------------------
/server-alice/src/main/resources/aliceprivate.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: DES-EDE3-CBC,2C075A54AADDF2B6
4 |
5 | /PhB7VsRJ+8nCCfZ8MAkI6HDRFhtqxqBaxlrCZEE1U7PKMVnjC/ABh59mB/Dtw/P
6 | WLGPfveYHfVqJLzMnrFuPlgbVaDGFmpry8gol1SBxFi7P2YowCcBilW2TYH2fe13
7 | 0vCsGY0sce8Pqu5u1TMUZ+AISGVrGrNyNdC99uy2j0XDWrwq3/RV+o4tAv4TdamW
8 | w4pVc6MpQkw8ywowmpbiWXiJdZyz5tDV4MTeUuFXErYTLIZBH7NZi0hgWjKYZXao
9 | HWOmX+EQWOw/MTJlUMfxQyuxZV9Ay8gxLOMJKuBZZbwcMvJzgcvtLpdVk6VhaDu7
10 | fjgm1sXtUDhmQsO6O6zYoDKFyI8AYVvN/5n5eXHygCCK5vOvXQYrff2htS3KHpjg
11 | 9qdMllVpNHJ/MxQ+c22h4Gu5Zsp3qr/+/91eBoEK9LRQT9Vk14HJaSNeH/Oj1hna
12 | pAuXSPW2F2IjyMnq3h87JMxjkYIHiQUBBOJCELCc63ihlMyWLXtnC1ZZh2oJc0KW
13 | LM1UCtgx3cLOxCAgRoi9OUeTyCLBUPxLWBxDVHLhFNa4ZXx+dHkY0/np5cBr7dGa
14 | 1ZxN4z1zNaUEFV4dDW3tV6S0onjvQJL6Rq6BmzRvZVV3BJIDiS51zHSW3q0cg4M1
15 | ZxwzM/tjszkDCk8CE9Rmr5/fxwYqVwDywUyEUIGwaCK/RDTY2mNYvNj2z/Ckr3Pw
16 | BugJMczZ3Gdf7qlGQriH2A7gy8iXg/aOoEAJSnxYTgdekNEr4uM0BGYhFEDJRbXl
17 | KJL1tpT0HwSwbvddqKuAHP4ZADq2iLr4ynp1ZF3VavQV66peDSVgog==
18 | -----END RSA PRIVATE KEY-----
19 |
--------------------------------------------------------------------------------
/server-alice/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8443
3 | ssl:
4 | key-store: classpath:alice-keystore.p12
5 | key-store-password: alicepassword
6 | trust-store: classpath:alice-truststore.jks
7 | trust-store-password: alicepassword
8 | client-auth: need
9 | security:
10 | headers:
11 | hsts: NONE
--------------------------------------------------------------------------------
/server-alice/src/test/java/de/jonashackt/RestClientCertTest.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import de.jonashackt.controller.ServerAliceController;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.boot.web.server.LocalServerPort;
8 | import org.springframework.test.context.junit4.SpringRunner;
9 | import org.springframework.web.client.RestTemplate;
10 |
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 |
13 |
14 | @SpringBootTest(
15 | classes = ServerAliceApplication.class,
16 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
17 | )
18 | public class RestClientCertTest {
19 |
20 | @LocalServerPort
21 | private int port;
22 |
23 | @Autowired
24 | private RestTemplate restTemplate;
25 |
26 | @Test
27 | public void is_hello_resource_callable_with_client_cert() {
28 | String response = restTemplate.getForObject("https://localhost:" + port + "/hello", String.class);
29 |
30 | assertEquals(ServerAliceController.RESPONSE, response);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/server-alice/src/test/java/de/jonashackt/RestClientCertTestConfiguration.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.impl.client.HttpClients;
5 | import org.apache.http.ssl.SSLContextBuilder;
6 | import org.springframework.boot.web.client.RestTemplateBuilder;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
10 | import org.springframework.util.ResourceUtils;
11 | import org.springframework.web.client.RestTemplate;
12 |
13 | import javax.net.ssl.SSLContext;
14 |
15 | @Configuration
16 | public class RestClientCertTestConfiguration {
17 |
18 | private char[] alicePassword = "alicepassword".toCharArray();
19 |
20 | @Bean
21 | public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
22 |
23 | SSLContext sslContext = SSLContextBuilder
24 | .create()
25 | .loadKeyMaterial(ResourceUtils.getFile("classpath:alice-keystore.p12"), alicePassword, alicePassword)
26 | .loadTrustMaterial(ResourceUtils.getFile("classpath:alice-truststore.jks"), alicePassword)
27 | .build();
28 |
29 | HttpClient client = HttpClients.custom()
30 | .setSSLContext(sslContext)
31 | .build();
32 |
33 | return builder
34 | .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
35 | .build();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server-alice/src/test/resources/alice-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-alice/src/test/resources/alice-keystore.p12
--------------------------------------------------------------------------------
/server-alice/src/test/resources/alice-truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-alice/src/test/resources/alice-truststore.jks
--------------------------------------------------------------------------------
/server-tom/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-alpine
2 |
3 | MAINTAINER Jonas Hecht
4 |
5 | VOLUME /tmp
6 |
7 | # Add Spring Boot app.jar to Container
8 | ADD "target/server-tom-0.0.1-SNAPSHOT.jar" app.jar
9 |
10 | ENV JAVA_OPTS=""
11 |
12 | # Fire up our Spring Boot app by default
13 | ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
--------------------------------------------------------------------------------
/server-tom/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | server-tom
7 | 0.0.1-SNAPSHOT
8 | jar
9 |
10 |
11 | de.jonashackt
12 | spring-boot-rest-clientcertificates-docker-compose
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 | UTF-8
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-web
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-security
31 |
32 |
33 |
34 |
35 | org.apache.httpcomponents
36 | httpclient
37 |
38 |
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-test
43 | test
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-maven-plugin
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/server-tom/src/main/java/de/jonashackt/ServerTomApplication.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class ServerTomApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(ServerTomApplication.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/server-tom/src/main/java/de/jonashackt/configuration/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.configuration;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
7 |
8 | @Configuration
9 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
10 |
11 | /*
12 | * Enable x509 client authentication.
13 | */
14 | @Override
15 | protected void configure(HttpSecurity http) throws Exception {
16 | http.x509();
17 | }
18 |
19 | /*
20 | * Create an in-memory authentication manager. We create 1 user (localhost which
21 | * is the CN of the client certificate) which has a role of USER.
22 | */
23 | @Override
24 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
25 | auth.inMemoryAuthentication()
26 | .withUser("localhost")
27 | .password("none")
28 | .roles("USER");
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server-tom/src/main/java/de/jonashackt/controller/ServerTomController.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt.controller;
2 |
3 | import org.springframework.web.bind.annotation.RequestMapping;
4 | import org.springframework.web.bind.annotation.RequestMethod;
5 | import org.springframework.web.bind.annotation.RestController;
6 |
7 | @RestController
8 | public class ServerTomController {
9 |
10 | public static final String RESPONSE = "Tom answering!";
11 |
12 | @RequestMapping(path="/hello", method=RequestMethod.GET)
13 | public String helloWorld() {
14 | System.out.println("Tom´s Server was called");
15 | return RESPONSE;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server-tom/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8443
3 | ssl:
4 | key-store: classpath:tom-keystore.p12
5 | key-store-password: tompassword
6 | trust-store: classpath:tom-truststore.jks
7 | trust-store-password: tompassword
8 | client-auth: need
9 | security:
10 | headers:
11 | hsts: NONE
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tom-csr.conf:
--------------------------------------------------------------------------------
1 | [req]
2 | distinguished_name = req_distinguished_name
3 | req_extensions = v3_req
4 | prompt = no
5 |
6 | [req_distinguished_name]
7 | C = DE
8 | ST = Thuringia
9 | L = Weimar
10 | O = Tom Inc.
11 | OU = Team Bar
12 | CN = server-tom
13 |
14 | [v3_req]
15 | keyUsage = keyEncipherment, dataEncipherment
16 | extendedKeyUsage = serverAuth
17 | subjectAltName = @alt_names
18 | [alt_names]
19 | DNS.1 = server-tom
20 | DNS.2 = localhost
21 |
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tom-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-tom/src/main/resources/tom-keystore.p12
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tom-truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-tom/src/main/resources/tom-truststore.jks
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tom.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICnjCCAgegAwIBAgIJAK5ZWgGKmkZ4MA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV
3 | BAYTAkRFMRIwEAYDVQQIDAlUaHVyaW5naWExDzANBgNVBAcMBldlaW1hcjERMA8G
4 | A1UECgwIVG9tIEluYy4xETAPBgNVBAsMCFRlYW0gQmFyMRMwEQYDVQQDDApzZXJ2
5 | ZXItdG9tMB4XDTE3MTIxMjE5MDcxNloXDTI3MTIxMDE5MDcxNlowbTELMAkGA1UE
6 | BhMCREUxEjAQBgNVBAgMCVRodXJpbmdpYTEPMA0GA1UEBwwGV2VpbWFyMREwDwYD
7 | VQQKDAhUb20gSW5jLjERMA8GA1UECwwIVGVhbSBCYXIxEzARBgNVBAMMCnNlcnZl
8 | ci10b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMdyqaiG7hggBSUcJD2l
9 | SjR9V4KdNG5G2iH3nBM6hq8E9BwY9oJMg9rwru4AX9G27owdbhv2hbFxnUDotECL
10 | UuW+M6QYSMSAgNmRRgYxrXf4VvYm2/cEPgCKf/FizeiVQdRmjcEueOmxwb8hdoWy
11 | bwFXtluXbclgHQH481mFmdF3AgMBAAGjRjBEMAsGA1UdDwQEAwIEMDATBgNVHSUE
12 | DDAKBggrBgEFBQcDATAgBgNVHREEGTAXggpzZXJ2ZXItdG9tgglsb2NhbGhvc3Qw
13 | DQYJKoZIhvcNAQEFBQADgYEAEviefsSN+QuvIw6SXMGJqM+Q9KD6TwEobQCGuTlA
14 | LYfjbhatT6JbArOWdXQVGNsuSYSjz0OMIBwj5ldGqqSCxj9XSlsHypQRJusYsNh6
15 | xGcsdwTqHqA7XwncfoWG+nIiZ1n0wSA5LgVNl+0My39/UKtXo4NNeUrKrwKM8si8
16 | lT8=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tom.csr:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE REQUEST-----
2 | MIICAjCCAWsCAQAwbTELMAkGA1UEBhMCREUxEjAQBgNVBAgMCVRodXJpbmdpYTEP
3 | MA0GA1UEBwwGV2VpbWFyMREwDwYDVQQKDAhUb20gSW5jLjERMA8GA1UECwwIVGVh
4 | bSBCYXIxEzARBgNVBAMMCnNlcnZlci10b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A
5 | MIGJAoGBAMdyqaiG7hggBSUcJD2lSjR9V4KdNG5G2iH3nBM6hq8E9BwY9oJMg9rw
6 | ru4AX9G27owdbhv2hbFxnUDotECLUuW+M6QYSMSAgNmRRgYxrXf4VvYm2/cEPgCK
7 | f/FizeiVQdRmjcEueOmxwb8hdoWybwFXtluXbclgHQH481mFmdF3AgMBAAGgVTBT
8 | BgkqhkiG9w0BCQ4xRjBEMAsGA1UdDwQEAwIEMDATBgNVHSUEDDAKBggrBgEFBQcD
9 | ATAgBgNVHREEGTAXggpzZXJ2ZXItdG9tgglsb2NhbGhvc3QwDQYJKoZIhvcNAQEL
10 | BQADgYEAcH5ASI8FNrOSdvAwuWeqt2BmAycBHSLGLkj46AuynoUl7ppxhSGAS5Id
11 | 2hs7lf3heqBXoxFMfBxQeaxZFGBKkrZSUf4sROQzlY+IiplgiJIZ23xULXRmpzeZ
12 | X5dvv3JhBhj7TeBEn84K+ETQVs/k79ONfqNtwf1MvkaJf4AYKaE=
13 | -----END CERTIFICATE REQUEST-----
14 |
--------------------------------------------------------------------------------
/server-tom/src/main/resources/tomprivate.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: DES-EDE3-CBC,7F219CD6B1FF6288
4 |
5 | r8ex7oDvrqu+bDR2X8S+VZ+7s+/ifZnVivJj2euUXmJ1B1DcD83b30CtpdF1955c
6 | G6hod+seAUwFfCVMEgli3tCP9suDrnzqdMfpDVr9iX++jcgDLfr7FqRfiTm5XqdF
7 | I20TEid0bFsjLlAXKEvU52im5nVXW4kWnI36kGZhmcTSQRrjtySVchqDnskxDP+l
8 | XPfmxKFLntoaYBdwTwcMsQYKsjZYH+zEhGP0UW8aALTn0Y4/2ZLm+vrMXzfiL+eY
9 | /0eTDVmffy1CadjYT9EP995oPK1tfgbUdD9g0i2z/4Ujy0ajECTgUB8KmGIL8Gf2
10 | I+jyG6zulTeTU2ELlv1xQalM0Mwnx0SD5abs0/n/jpgLH4DyAod0wcPvCqbBWPvU
11 | n1iDhApjshbRZ+4CbKleCWJ2inIKwD0GUbtB3h97CfGzU+ItWC2rX+qeCGtE5ac4
12 | DBAuyqrdkbHS/DxraiXUYjEYEEQX/NuTi6iUfEvGYsudybkQBEvkt6uMZg8HahS9
13 | fivQDojIWU6Y38qcFE3B5Yc1vIelYw5bwy1HHBsSzoLr3qKQ9rX1g580s7F3eonQ
14 | LlVJuN2xqfVqAenDvdECFsP/eoXVVWyj9ks/5IbHmENHEZ4oui6RxdzLjfM2nvVw
15 | naTPZt+ewAytYqxuzRink4+G9YWHqRJXkO95Dxpf79GdbiZlO7Bgu9s6if5ikC8/
16 | ZSeH1aPWa1mmjfrRJoilcdV969jM/6W7jx/Wwh6j1h34jFtAwgPx8D77WkPDbOQP
17 | 5slc6yuLcUd2mznMiFmdIjCnspWM6RN4gc6aI9nytcdaT2a4oC7kkA==
18 | -----END RSA PRIVATE KEY-----
19 |
--------------------------------------------------------------------------------
/server-tom/src/test/java/de/jonashackt/RestClientCertTest.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import de.jonashackt.controller.ServerTomController;
4 |
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.boot.web.server.LocalServerPort;
9 | import org.springframework.test.context.junit4.SpringRunner;
10 | import org.springframework.web.client.RestTemplate;
11 |
12 | import static org.junit.jupiter.api.Assertions.assertEquals;
13 |
14 |
15 | @SpringBootTest(
16 | classes = ServerTomApplication.class,
17 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
18 | )
19 | public class RestClientCertTest {
20 |
21 | @LocalServerPort
22 | private int port;
23 |
24 | @Autowired
25 | private RestTemplate restTemplate;
26 |
27 | @Test
28 | public void is_hello_resource_callable_with_client_cert() {
29 | String response = restTemplate.getForObject("https://localhost:" + port + "/hello", String.class);
30 |
31 | assertEquals(ServerTomController.RESPONSE, response);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server-tom/src/test/java/de/jonashackt/RestClientCertTestConfiguration.java:
--------------------------------------------------------------------------------
1 | package de.jonashackt;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.impl.client.HttpClients;
5 | import org.apache.http.ssl.SSLContextBuilder;
6 | import org.springframework.boot.web.client.RestTemplateBuilder;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
10 | import org.springframework.util.ResourceUtils;
11 | import org.springframework.web.client.RestTemplate;
12 |
13 | import javax.net.ssl.SSLContext;
14 |
15 | @Configuration
16 | public class RestClientCertTestConfiguration {
17 |
18 | private char[] tomPassword = "tompassword".toCharArray();
19 |
20 | @Bean
21 | public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
22 |
23 | SSLContext sslContext = SSLContextBuilder
24 | .create()
25 | .loadKeyMaterial(ResourceUtils.getFile("classpath:tom-keystore.p12"), tomPassword, tomPassword)
26 | .loadTrustMaterial(ResourceUtils.getFile("classpath:tom-truststore.jks"), tomPassword)
27 | .build();
28 |
29 | HttpClient client = HttpClients.custom()
30 | .setSSLContext(sslContext)
31 | .build();
32 |
33 | return builder
34 | .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
35 | .build();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server-tom/src/test/resources/tom-keystore.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-tom/src/test/resources/tom-keystore.p12
--------------------------------------------------------------------------------
/server-tom/src/test/resources/tom-truststore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose/7ba4fe11631b9ab79c544239b5ad60739cb8e49d/server-tom/src/test/resources/tom-truststore.jks
--------------------------------------------------------------------------------