Skip to content

Instantly share code, notes, and snippets.

@neolaw84
Last active August 15, 2022 17:02
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save neolaw84/9334007e67752a957394ac2408d543e8 to your computer and use it in GitHub Desktop.
Save neolaw84/9334007e67752a957394ac2408d543e8 to your computer and use it in GitHub Desktop.
Creating a Spring Boot Web (REST) service with SSL

Creating a Spring Boot Web (REST) service with SSL

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

Situation/Problem

To create an SSL secured web (REST) service with cliet authentication (X509) using Spring Boot.

Action/Solution

Enabling SSL in Server-Side

  1. 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').

  1. 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
  1. 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>

Accessing the Server via RestTemplate

  1. 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
  1. 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);
	}

Enabling SSL with Client Authentication

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)

  1. generate two private keys (node1.key and node2.key)

  2. generate two certificate signing requests (node1.csr and node2.csr)

  3. generate cross-signed certificates (node1.crt and node2.crt)

  4. generate self-signed certificates (node11.crt and node22.crt)

  5. put the cross-signed certificates into trust-stores (node1.crt to node2.jks and vice-versa)

  6. put the self-signed certificates into key-stores (node11.crt to node1.p12 and vice-versa)

  7. 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
  1. Run the application.

Accessing the Server (that need client-authentication) via RestTemplate

  1. 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);
	}

Creating a Spring Boot Web (REST) service with SSL

We will refer to: https://www.baeldung.com/spring-boot-https-self-signed-certificate

Situation/Problem

To create an SSL secured web (REST) service using Spring Boot.

Action/Solution

  1. We will create a pkcs12 keystore containing key qbpo.
keytool -genkeypair -alias qbpo -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore qbpo_java.p12 -validity 3650

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').

  1. We will put the following properties in the application.properties to so that generated keystore will be used by the app.
# 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:qbpo_java.p12
# The password used to generate the certificate
server.ssl.key-store-password=abcd1234
# The alias mapped to the certificate
server.ssl.key-alias=qbpo
  1. Run the application.

Results

The resulting application is using the keystore and SSL secured (https).

Configuration of Clients for the Spring Boot Web (REST) with SSL

Situation/Problem

We need a java program that can hit REST API behind SSL.

Action/Solution

We need to run java program using a trust-store (not a key-store). Here is how:

  1. Get the certificate from the web-serviec (using browser like Chrome)

  2. Create a trust-store

keytool -import -alias ca -file somecert.cer -keystore cacerts -storepass changeit
  1. Start using the trust-store (for example, run the following JUnit test).
@SpringBootTest
@RunWith(SpringRunner.class)
public class SWOControllerViaRestTests {
	
	@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 has certificate for 'localhost' with password 'changeit'
	 */
	@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);
	}
}

Result

Resulting java program can hit REST API (behind SSL).

Creating a Spring Boot Web (REST) service with SSL and Client Authentication

We will refer to: https://dzone.com/articles/securing-rest-apis-with-client-certificates

Situation/Problem

We need to set up a Spring Boot Web (REST) service with both SSL and Client Authentication (two-way authentication or X509 authentication).

Action/Solution

  1. We create necessary keys, certificates and keystores.
./04_gen_certs.sh

Note: the commands in the source (dzone) link has typos. Refer to the 04_gen_certs.sh for typo-clear version.

In this exercise, we make sure both common names are 'localhost' and passwords are 'abcd1234'

  1. Add the following properties to application.properties
# 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=file:/path/to/ssl/server/keyStore.p12
# The password used to generate the certificate
server.ssl.key-store-password=abcd1234

# Trust store that holds SSL certificates.
server.ssl.trust-store=file:/path/to/ssl/server/trustStore.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
  1. Run the service.

  2. Install the /path/to/ssl/client/client_pavel.p12 (in windows, double-clicking will do).

  3. Try the service from browser. The browser will ask which certificate to use (you will see 'localhost' there).

#Create folders to generate all files (separated for client and server)
mkdir ssl && cd ssl && mkdir client && mkdir server
## Server
# Generate server private key and self-signed certificate in one step
openssl req -x509 -newkey rsa:4096 -keyout server/serverPrivateKey.pem -out server/server.crt -days 3650 -nodes
# Create PKCS12 keystore containing private key and related self-sign certificate
openssl pkcs12 -export -out server/keyStore.p12 -inkey server/serverPrivateKey.pem -in server/server.crt
# Generate server trust store from server certificate
keytool -import -trustcacerts -alias root -file server/server.crt -keystore server/trustStore.jks
## Client
# Generate client's private key and a certificate signing request (CSR)
openssl req -new -newkey rsa:4096 -out client/request.csr -keyout client/myPrivateKey.pem -nodes
## Server
# Sign client's CSR with server private key and a related certificate
openssl x509 -req -days 360 -in client/request.csr -CA server/server.crt -CAkey server/serverPrivateKey.pem -CAcreateserial -out client/pavel.crt -sha256
## Client
# Verify client's certificate
openssl x509 -text -noout -in client/pavel.crt
# Create PKCS12 keystore containing client's private key and related self-sign certificate
openssl pkcs12 -export -out client/client_pavel.p12 -inkey client/myPrivateKey.pem -in client/pavel.crt -certfile server/server.crt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment