├── 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 | --------------------------------------------------------------------------------