├── bin
├── client-public.cer
├── server-public.cer
└── gen-non-prod-key.sh
├── client
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── client-nonprod.jks
│ │ │ └── application.properties
│ │ └── java
│ │ │ └── com
│ │ │ └── plumstep
│ │ │ ├── controller
│ │ │ └── ClientController.java
│ │ │ ├── config
│ │ │ └── SecurityConfig.java
│ │ │ └── ClientApplication.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── plumstep
│ │ └── ClientIntegrationTest.java
└── pom.xml
├── server
├── src
│ └── main
│ │ ├── resources
│ │ ├── server-nonprod.jks
│ │ └── application.properties
│ │ └── java
│ │ └── com
│ │ └── plumstep
│ │ ├── controller
│ │ └── ServerController.java
│ │ ├── config
│ │ └── SecurityConfig.java
│ │ └── ServerApplication.java
└── pom.xml
├── pom.xml
├── LICENSE
└── README.md
/bin/client-public.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joutwate/mtls-springboot/HEAD/bin/client-public.cer
--------------------------------------------------------------------------------
/bin/server-public.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joutwate/mtls-springboot/HEAD/bin/server-public.cer
--------------------------------------------------------------------------------
/client/src/main/resources/client-nonprod.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joutwate/mtls-springboot/HEAD/client/src/main/resources/client-nonprod.jks
--------------------------------------------------------------------------------
/server/src/main/resources/server-nonprod.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joutwate/mtls-springboot/HEAD/server/src/main/resources/server-nonprod.jks
--------------------------------------------------------------------------------
/client/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #debug=true
2 |
3 | spring.application.name=client
4 | spring.profiles.active=default
5 |
6 | server.ssl.key-store=classpath:client-nonprod.jks
7 | server.ssl.key-store-password=changeme
8 | server.ssl.key-password=changeme
9 | server.ssl.trust-store=classpath:client-nonprod.jks
10 | server.ssl.trust-store-password=changeme
11 | # Mutual TLS/SSL
12 | server.ssl.client-auth=need
13 | server.port=8222
14 |
--------------------------------------------------------------------------------
/server/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #debug=true
2 |
3 | spring.application.name=server
4 | spring.profiles.active=default
5 |
6 | server.ssl.key-store=classpath:server-nonprod.jks
7 | server.ssl.key-store-password=changeme
8 | server.ssl.key-password=changeme
9 | server.ssl.trust-store=classpath:server-nonprod.jks
10 | server.ssl.trust-store-password=changeme
11 | # Mutual TLS/SSL
12 | server.ssl.client-auth=need
13 | server.port=8111
14 |
--------------------------------------------------------------------------------
/client/src/main/java/com/plumstep/controller/ClientController.java:
--------------------------------------------------------------------------------
1 | package com.plumstep.controller;
2 |
3 | import io.swagger.annotations.ApiOperation;
4 | import org.springframework.http.ResponseEntity;
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 | @RequestMapping("client")
11 | public class ClientController {
12 | @ApiOperation(value = "Return text message to show off successful call")
13 | @RequestMapping(value = "/", method = RequestMethod.GET)
14 | ResponseEntity> getMessage() {
15 | return ResponseEntity.ok("Client successfully called!");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/src/main/java/com/plumstep/controller/ServerController.java:
--------------------------------------------------------------------------------
1 | package com.plumstep.controller;
2 |
3 | import io.swagger.annotations.ApiOperation;
4 | import org.springframework.http.ResponseEntity;
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 | @RequestMapping("server")
11 | public class ServerController {
12 | @ApiOperation(value = "Return text message to show off successful call")
13 | @RequestMapping(value = "/", method = RequestMethod.GET)
14 | ResponseEntity> getMessage() {
15 | return ResponseEntity.ok("Server successfully called!");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.plumstep
7 | mtls-springboot
8 | 1.0-SNAPSHOT
9 | pom
10 |
11 | Mutual TLS
12 | Mutual TLS SpringBoot Example
13 |
14 |
15 | UTF-8
16 | 1.8
17 |
18 |
19 |
20 | client
21 | server
22 |
23 |
24 |
--------------------------------------------------------------------------------
/client/src/main/java/com/plumstep/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.plumstep.config;
2 |
3 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
6 | import org.springframework.security.config.http.SessionCreationPolicy;
7 |
8 | @EnableWebSecurity
9 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
10 | protected void configure(HttpSecurity http) throws Exception {
11 | // Disable http basic authentication since we are using client auth.
12 | http.httpBasic().disable();
13 |
14 | // Turn off CSRF and session management for this REST service.
15 | http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/server/src/main/java/com/plumstep/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.plumstep.config;
2 |
3 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
6 | import org.springframework.security.config.http.SessionCreationPolicy;
7 |
8 | @EnableWebSecurity
9 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
10 | protected void configure(HttpSecurity http) throws Exception {
11 | // Disable http basic authentication since we are using client auth.
12 | http.httpBasic().disable();
13 |
14 | // Turn off CSRF and session management for this REST service.
15 | http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/client/src/test/java/com/plumstep/ClientIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.plumstep;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.test.context.ActiveProfiles;
11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | import static org.junit.Assert.assertEquals;
15 |
16 | @ActiveProfiles("default")
17 | @RunWith(SpringJUnit4ClassRunner.class)
18 | @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, classes = {ClientApplication.class})
19 | public class ClientIntegrationTest {
20 |
21 | @Autowired
22 | private RestTemplate restTemplate;
23 |
24 | private String serverUrl = "https://localhost:8111/server/";
25 |
26 | @Test
27 | public void testGet() {
28 | ResponseEntity getResponse =
29 | restTemplate.getForEntity(serverUrl, String.class);
30 | assertEquals(HttpStatus.OK, getResponse.getStatusCode());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/bin/gen-non-prod-key.sh:
--------------------------------------------------------------------------------
1 | CLIENT_KEYSTORE_DIR=../client/src/main/resources
2 | SERVER_KEYSTORE_DIR=../server/src/main/resources
3 | CLIENT_KEYSTORE=$CLIENT_KEYSTORE_DIR/client-nonprod.jks
4 | SERVER_KEYSTORE=$SERVER_KEYSTORE_DIR/server-nonprod.jks
5 | JAVA_CA_CERTS=$JAVA_HOME/jre/lib/security/cacerts
6 |
7 | # Generate a client and server RSA 2048 key pair
8 | keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -dname "CN=Client,OU=Client,O=PlumStep,L=San Francisco,S=CA,C=U" -keypass changeme -keystore $CLIENT_KEYSTORE -storepass changeme
9 | keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -dname "CN=Server,OU=Server,O=PlumStep,L=San Francisco,S=CA,C=U" -keypass changeme -keystore $SERVER_KEYSTORE -storepass changeme
10 |
11 | # Export public certificates for both the client and server
12 | keytool -exportcert -alias client -file client-public.cer -keystore $CLIENT_KEYSTORE -storepass changeme
13 | keytool -exportcert -alias server -file server-public.cer -keystore $SERVER_KEYSTORE -storepass changeme
14 |
15 | # Import the client and server public certificates into each others keystore
16 | keytool -importcert -keystore $CLIENT_KEYSTORE -alias server-public-cert -file server-public.cer -storepass changeme -noprompt
17 | keytool -importcert -keystore $SERVER_KEYSTORE -alias client-public-cert -file client-public.cer -storepass changeme -noprompt
--------------------------------------------------------------------------------
/client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.plumstep
7 | client
8 | 1.0-SNAPSHOT
9 | jar
10 |
11 | Client
12 | Client SpringBoot Application
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 1.4.0.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | 1.8
24 |
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-web
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-security
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-data-jpa
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-configuration-processor
46 | true
47 |
48 |
49 |
50 | org.apache.httpcomponents
51 | httpclient
52 | 4.5.13
53 |
54 |
55 | com.h2database
56 | h2
57 |
58 |
59 |
60 |
61 | io.springfox
62 | springfox-swagger-ui
63 | 2.5.0
64 | compile
65 |
66 |
67 | io.springfox
68 | springfox-swagger2
69 | 2.5.0
70 | compile
71 |
72 |
73 |
74 |
75 | org.springframework.boot
76 | spring-boot-starter-test
77 | test
78 |
79 |
80 |
81 |
82 |
83 |
84 | org.springframework.boot
85 | spring-boot-maven-plugin
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/client/src/main/java/com/plumstep/ClientApplication.java:
--------------------------------------------------------------------------------
1 | package com.plumstep;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.conn.ssl.NoopHostnameVerifier;
5 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
6 | import org.apache.http.impl.client.HttpClients;
7 | import org.apache.http.ssl.SSLContexts;
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.boot.SpringApplication;
10 | import org.springframework.boot.autoconfigure.SpringBootApplication;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.core.io.Resource;
13 | import org.springframework.http.HttpStatus;
14 | import org.springframework.http.client.ClientHttpRequestFactory;
15 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
16 | import org.springframework.web.client.DefaultResponseErrorHandler;
17 | import org.springframework.web.client.RestTemplate;
18 | import springfox.documentation.builders.PathSelectors;
19 | import springfox.documentation.builders.RequestHandlerSelectors;
20 | import springfox.documentation.spi.DocumentationType;
21 | import springfox.documentation.spring.web.plugins.Docket;
22 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
23 |
24 | import javax.net.ssl.SSLContext;
25 |
26 | @SpringBootApplication
27 | @EnableSwagger2
28 | public class ClientApplication {
29 | @Value("${server.ssl.trust-store-password}")
30 | private String trustStorePassword;
31 | @Value("${server.ssl.trust-store}")
32 | private Resource trustStore;
33 | @Value("${server.ssl.key-store-password}")
34 | private String keyStorePassword;
35 | @Value("${server.ssl.key-password}")
36 | private String keyPassword;
37 | @Value("${server.ssl.key-store}")
38 | private Resource keyStore;
39 |
40 | public static void main(String[] args) {
41 | SpringApplication.run(ClientApplication.class, args);
42 | }
43 |
44 | @Bean
45 | public Docket api() {
46 | return new Docket(DocumentationType.SWAGGER_2)
47 | .select()
48 | .apis(RequestHandlerSelectors.any())
49 | .paths(PathSelectors.any())
50 | .build();
51 | }
52 |
53 | @Bean
54 | public RestTemplate restTemplate() throws Exception {
55 | RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
56 | restTemplate.setErrorHandler(
57 | new DefaultResponseErrorHandler() {
58 | @Override
59 | protected boolean hasError(HttpStatus statusCode) {
60 | return false;
61 | }
62 | });
63 |
64 | return restTemplate;
65 | }
66 |
67 | private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
68 | return new HttpComponentsClientHttpRequestFactory(httpClient());
69 | }
70 |
71 | private HttpClient httpClient() throws Exception {
72 | // Load our keystore and truststore containing certificates that we trust.
73 | SSLContext sslcontext =
74 | SSLContexts.custom().loadTrustMaterial(trustStore.getFile(), trustStorePassword.toCharArray())
75 | .loadKeyMaterial(keyStore.getFile(), keyStorePassword.toCharArray(),
76 | keyPassword.toCharArray()).build();
77 | SSLConnectionSocketFactory sslConnectionSocketFactory =
78 | new SSLConnectionSocketFactory(sslcontext, new NoopHostnameVerifier());
79 | return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/server/src/main/java/com/plumstep/ServerApplication.java:
--------------------------------------------------------------------------------
1 | package com.plumstep;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.conn.ssl.NoopHostnameVerifier;
5 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
6 | import org.apache.http.impl.client.HttpClients;
7 | import org.apache.http.ssl.SSLContexts;
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.boot.SpringApplication;
10 | import org.springframework.boot.autoconfigure.SpringBootApplication;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.core.io.Resource;
13 | import org.springframework.http.HttpStatus;
14 | import org.springframework.http.client.ClientHttpRequestFactory;
15 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
16 | import org.springframework.web.client.DefaultResponseErrorHandler;
17 | import org.springframework.web.client.RestTemplate;
18 | import springfox.documentation.builders.PathSelectors;
19 | import springfox.documentation.builders.RequestHandlerSelectors;
20 | import springfox.documentation.spi.DocumentationType;
21 | import springfox.documentation.spring.web.plugins.Docket;
22 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
23 |
24 | import javax.net.ssl.SSLContext;
25 |
26 | @SpringBootApplication
27 | @EnableSwagger2
28 | public class ServerApplication {
29 | @Value("${server.ssl.trust-store-password}")
30 | private String trustStorePassword;
31 | @Value("${server.ssl.trust-store}")
32 | private Resource trustResource;
33 | @Value("${server.ssl.key-store-password}")
34 | private String keyStorePassword;
35 | @Value("${server.ssl.key-password}")
36 | private String keyPassword;
37 | @Value("${server.ssl.key-store}")
38 | private Resource keyStore;
39 |
40 | public static void main(String[] args) {
41 | SpringApplication.run(ServerApplication.class, args);
42 | }
43 |
44 | @Bean
45 | public Docket api() {
46 | return new Docket(DocumentationType.SWAGGER_2)
47 | .select()
48 | .apis(RequestHandlerSelectors.any())
49 | .paths(PathSelectors.any())
50 | .build();
51 | }
52 |
53 | @Bean
54 | public RestTemplate restTemplate() throws Exception {
55 | RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
56 | restTemplate.setErrorHandler(
57 | new DefaultResponseErrorHandler() {
58 | @Override
59 | protected boolean hasError(HttpStatus statusCode) {
60 | return false;
61 | }
62 | });
63 |
64 | return restTemplate;
65 | }
66 |
67 | private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
68 | return new HttpComponentsClientHttpRequestFactory(httpClient());
69 | }
70 |
71 | private HttpClient httpClient() throws Exception {
72 | // Load our keystore and truststore containing certificates that we trust.
73 | SSLContext sslcontext =
74 | SSLContexts.custom().loadTrustMaterial(trustResource.getFile(), trustStorePassword.toCharArray())
75 | .loadKeyMaterial(keyStore.getFile(), keyStorePassword.toCharArray(),
76 | keyPassword.toCharArray()).build();
77 | SSLConnectionSocketFactory sslConnectionSocketFactory =
78 | new SSLConnectionSocketFactory(sslcontext, new NoopHostnameVerifier());
79 | return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.plumstep
7 | server
8 | 1.0-SNAPSHOT
9 | jar
10 |
11 | Server
12 | Server SPringBoot Application
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 1.4.0.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | 1.8
24 |
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-web
30 |
31 |
32 |
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-security
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-data-jpa
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-configuration-processor
46 | true
47 |
48 |
49 |
50 | org.apache.httpcomponents
51 | httpclient
52 | 4.5.13
53 |
54 |
55 | com.h2database
56 | h2
57 |
58 |
59 | com.fasterxml.jackson.datatype
60 | jackson-datatype-joda
61 | 2.7.5
62 |
63 |
64 |
65 |
66 | io.springfox
67 | springfox-swagger-ui
68 | 2.5.0
69 | compile
70 |
71 |
72 | io.springfox
73 | springfox-swagger2
74 | 2.5.0
75 | compile
76 |
77 |
78 |
79 |
80 | mysql
81 | mysql-connector-java
82 | 8.0.16
83 | runtime
84 |
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-starter-test
89 | test
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-maven-plugin
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Example of Mutual Client Authorization in SpringBoot
2 |
3 | (Check out what I've been up to lately at https://99milestoempty.com/)
4 |
5 | **Quickstart**
6 |
7 | Generate keystores and run server app followed by client test case.
8 | ```
9 | cd mtls-springboot/bin
10 | sh -x ./gen-non-prod-key.sh
11 | cd ../server
12 | mvn spring-boot:run
13 | # In another shell
14 | cd mtls-springboot/client
15 | mvn test
16 | ```
17 |
18 | **Summary**
19 |
20 | This demo contains two SpringBoot applications that can be run to demonstrate mutual authorization. For mutual
21 | authorization to occur the client must validate the server and the server must validate the client.
22 |
23 | The first step, client validating the server, happens during the initial https request. In our example the client
24 | application's keystore `client-nonprod.jks` contains the server's public certificate. We use our keystore as a
25 | truststore so certificates returned from a server during a https request will be validated against our set of trusted
26 | certificates. The full code for this can be seen in `ClientApplication.java`.
27 | ```
28 | @Value("${server.ssl.trust-store-password}")
29 | private String trustStorePassword;
30 | @Value("${server.ssl.trust-store}")
31 | private Resource trustStore;
32 |
33 | ...
34 |
35 | // Load our trust store and key store containing certificates that we trust.
36 | SSLContext sslcontext =
37 | SSLContexts.custom().loadTrustMaterial(trustStore.getFile(), trustStorePassword.toCharArray())
38 | .loadKeyMaterial(keyStore.getFile(), keyStorePassword.toCharArray(),
39 | keyPassword.toCharArray()).build();
40 | ```
41 |
42 |
43 | The second step, server validating the client, happens when the server validates the client's public certificate during
44 | the TLSv1.2 Handshake (turn debugging on so you can see the following messages `-Djavax.net.debug=all`).
45 |
46 | The client will send its public certificate
47 | ```
48 | https-jsse-nio-8111-exec-4, READ: TLSv1.2 Handshake, length = 959
49 | *** Certificate chain
50 | chain [0] = [
51 | [
52 | Version: V3
53 | Subject: CN=Client, OU=Client, O=PlumStep, L=San Francisco, ST=CA, C=U
54 | Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
55 |
56 | Key: Sun RSA public key, 2048 bits
57 | modulus: 23924815366191671527685604468707756921920367435263691229358087731529862243742791861749857441668937465882655232936400385161742990710489227264008217666996198481503102758954806108248423197429783435192038688889492757756275150038243065757169401424091252018478837180900174378000961676564908357097778694464840754820934412558396246978777685819277422908337420810360977522693088849841753891663744703592285824763759784328896409676461105804402578955544664436941232613361320667903301393140906588668044451841493172709143139866499595316834004329321057012333974423931211783507609409810080956508128654820187759105126624405395976512597
58 | public exponent: 65537
59 | Validity: [From: Tue Aug 30 20:46:33 PDT 2016,
60 | To: Mon Nov 28 19:46:33 PST 2016]
61 | Issuer: CN=Client, OU=Client, O=PlumStep, L=San Francisco, ST=CA, C=U
62 | SerialNumber: [ 659d8227]
63 | ```
64 |
65 | and then the server will validate that certificate against its truststore. Since the server's truststore contains
66 | the client's public certificate it will find it and validate it.
67 |
68 | ```
69 | ***
70 | Found trusted certificate:
71 | [
72 | [
73 | Version: V3
74 | Subject: CN=Client, OU=Client, O=PlumStep, L=San Francisco, ST=CA, C=U
75 | Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
76 |
77 | Key: Sun RSA public key, 2048 bits
78 | modulus: 23924815366191671527685604468707756921920367435263691229358087731529862243742791861749857441668937465882655232936400385161742990710489227264008217666996198481503102758954806108248423197429783435192038688889492757756275150038243065757169401424091252018478837180900174378000961676564908357097778694464840754820934412558396246978777685819277422908337420810360977522693088849841753891663744703592285824763759784328896409676461105804402578955544664436941232613361320667903301393140906588668044451841493172709143139866499595316834004329321057012333974423931211783507609409810080956508128654820187759105126624405395976512597
79 | public exponent: 65537
80 | Validity: [From: Tue Aug 30 20:46:33 PDT 2016,
81 | To: Mon Nov 28 19:46:33 PST 2016]
82 | Issuer: CN=Client, OU=Client, O=PlumStep, L=San Francisco, ST=CA, C=U
83 | SerialNumber: [ 659d8227]
84 | ```
85 |
86 | The code for how the server loads its keystore and truststore is the same as in the client app and can be found in
87 | `ServerApplication.java`.
88 |
89 | The *default* profiles located at `src/main/resources/application.properties` under both the server and client
90 | configures the SpringBoot applications to use SSL and to *need* client authorization via
91 | `server.ssl.client-auth=need`. You can also configure the server to `want` client authorization which will allow clients
92 | to access the server without a certificate however this is dangerous if you do not have any other means of
93 | authorization in your app.
94 |
95 | **Notes**
96 |
97 | 1. The client application is a full SpringBoot app however we are just using the integration test support in
98 | SpringBoot as an easy way to make a secure connection to the server.
99 |
100 | 2. To run this example you will first need to create a keystore with a public/private key for both the client and server
101 | and import their public certificates in to the respective keystores. A bash script is available `bin/gen-non-prod-key.sh`
102 | which will do this for you. Re-running the script is non-destructive since keytool will not overwrite existing data.
103 | You must have the environment variable `JAVA_HOME` set and you **MUST** run the script from the bin directory.
104 |
105 | 3. With logging turned on via `-Djavax.net.debug=all` you will be able to see that the server will validate the client
106 | certificate during a request
107 |
108 | 4. The password for the keystores is **changeme**
109 |
110 | **Things to try**
111 |
112 | 1. Attempt to access the server's endpoint via a browser https://localhost:8111/server/ (**the trailing / is necessary**)
113 | 2. Remove the server public cert from the client's keystore and re-run.
114 | 3. Remove the client public cert from the server's keystore and re-run (**make sure to restore the server's public cert
115 | first if you did #2**).
116 |
--------------------------------------------------------------------------------