We will refer to: https://github.com/jonashackt/spring-boot-rest-clientcertificate
Full project is available at: https://github.com/neolaw84/spring_boot_web_with_ssl
To create an SSL secured web (REST) service with cliet authentication (X509) using Spring Boot.
- We will create a private key, certificate signing request and a self-signed certificate.
#!/bin/bash
echo "generating key 1"
openssl genrsa -des3 -out node1.key 1024
echo "generating csr 1"
openssl req -new -key node1.key -out node1.csr
echo "generating certificate 1 signed by key 1"
openssl x509 -req -days 3650 -in node1.csr -signkey node1.key -out node11.crt
It will ask several questions including password for keystore. We will make the password 'abcd1234' for this example.
It is very important to match First Name Last Name is the same as the host-name of your server. If you are playing in localhost, enter "localhost" as your name. (i.e. CN=localhost in verification step, where you enter 'yes').
- We will create a pkcs12 key-store that houses self-signed certificate node11.crt.
echo "generating key-store 1 containing certificate 1 signed by key 1"
openssl pkcs12 -export -in node11.crt -inkey node1.key -name "node1" -out node1.p12
- Use the following application.properties
# The format used for the keystore. It could be set to JKS in case it is a JKS file
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=classpath:path/to/node1.p12
# The password used to generate the certificate
server.ssl.key-store-password=<password>
- Import the self-signed certificate to node22.jks to be used as trust-store.
echo "generating trust-store 2 containing certificate 1 signed by key 2"
keytool -import -file node11.crt -alias node1 -keystore node22.jks
- Use the following code-fragment:
@Autowired RestTemplateBuilder restTemplateBuilder;
/**
* This test case requires the followings to be put as VM Arguments:
* -Djavax.net.ssl.trustStore="full/path/to/cacerts" -Djavax.net.ssl.trustStorePassword="password"
* the one in git repo (src/main/resources/certs3/node22.jks has certificate for 'localhost' with password 'abcd1234'
*/
@Test
public void addTwoAndFive() {
UriComponentsBuilder uriComponentBuilder = UriComponentsBuilder.fromHttpUrl(
"https://localhost:8080/integer/add_two/"
)
.queryParam("a", new Integer(2))
.queryParam("b", new Integer(5));
RestTemplate restTemplate = restTemplateBuilder.build();
String uriString = uriComponentBuilder.toUriString();
System.out.println(uriString);
SwoIntegerObject result = restTemplate.getForObject(
uriString, SwoIntegerObject.class
);
assert(result.getInteger() == 7);
}
We will do the following steps (alternatively, just run https://github.com/neolaw84/spring_boot_web_with_ssl/blob/master/src/main/resources/certs3/gen_certs_pairs.sh)
-
generate two private keys (node1.key and node2.key)
-
generate two certificate signing requests (node1.csr and node2.csr)
-
generate cross-signed certificates (node1.crt and node2.crt)
-
generate self-signed certificates (node11.crt and node22.crt)
-
put the cross-signed certificates into trust-stores (node1.crt to node2.jks and vice-versa)
-
put the self-signed certificates into key-stores (node11.crt to node1.p12 and vice-versa)
-
Use node1.p12 as keystore and node1.jks as trust-store in server. Let the client use node22.crt self-signed certificate and cross-signed certificate node1.crt (node2.p12 and node22.jks)
# Define a custom port (instead of the default 8080)
server.port=8443
# The format used for the keystore
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=classpath:certs3/node1.p12
# The password used to generate the certificate
server.ssl.key-store-password=abcd1234
# Trust store that holds SSL certificates.
server.ssl.trust-store=classpath:/certs3/node11.jks
# Password used to access the trust store.
server.ssl.trust-store-password=abcd1234
# Type of the trust store.
server.ssl.trust-store-type=JKS
# Whether client authentication is wanted ("want") or needed ("need").
server.ssl.client-auth=need
- Run the application.
- Use the following code-fragment (better to make the RestTemplate a bean):
@Autowired RestTemplateBuilder restTemplateBuilder;
@Value(value="${server.ssl.key-store}") Resource keyStore = null;
@Value(value="${server.ssl.trust-store}") Resource trustStore = null;
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(keyStore.getFile(), "abcd1234".toCharArray(), "abcd1234".toCharArray())
.loadTrustMaterial(trustStore.getFile(), "abcd1234".toCharArray())
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
.build();
}
/**
* This test case requires the followings to be put as VM Arguments:
* -Djavax.net.ssl.trustStore="full/path/to/cacerts" -Djavax.net.ssl.trustStorePassword="password"
* the "node2.jks" in git repo has certificate for 'localhost' with password 'changeit'
* @throws Exception
* @throws RestClientException
*/
@Test
public void addTwoAndFive() throws RestClientException, Exception {
UriComponentsBuilder uriComponentBuilder = UriComponentsBuilder.fromHttpUrl(
"https://localhost:8443/integer/add_two/"
)
.queryParam("a", new Integer(2))
.queryParam("b", new Integer(5));
//RestTemplate restTemplate = restTemplateBuilder.build();
String uriString = uriComponentBuilder.toUriString();
System.out.println(uriString);
SwoIntegerObject result = restTemplate(restTemplateBuilder)
.getForObject(
uriString, SwoIntegerObject.class
);
assert(result.getInteger() == 7);
}