REST over Https with Client certificate Authentication

Introduction

The tutorial, REST over HTTPS with client certificate authentication, will show you how we can use client certificate to handshake with server along with basic authentication for consuming the service. Even you can use header authentication along with client certificate to make more secure. I will create Spring Boot application on Spring REST API and build the application using both maven and gradle build tools.

I will show here both server side code and client side code using Spring Boot framework so that server expects client to establish communication through certificate authentication. Here to consume the service you will be given client certificate (extention might be .crt or .der or .p12 or anything else), password for this certificate and username/password for basic authentication (in case if you need also header authentication).

The certificate we are going to use is generated by ourselves using the Java’s built-in tool utility called keytool.

Prerequisites

Eclipse 2019-12, At least Java 8, Gradle 6.1.1, Maven 3.6.3, Spring Boot 2.2.4

We are going to create two separate Spring Boot applications, one is REST Server (server application) and another one is REST Client (client application).

REST Server

This the server project using Spring Boot framework which will expose REST API endpoint on http/https port and this endpoint will be called from the client application.

Create Project

First step is to create a project in Eclipse. The name of the project is spring-rest-https-server.

If you are creating a gradle based project then you can use the below build.gradle script.

buildscript {
	ext {
		springBootVersion = '2.2.4.RELEASE'
	}
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
}

If you are creating maven based project then you can use the below pom.xml file.

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-rest-https-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

    <build>
        <plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>at least 8</source>
					<target>at least 8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

REST Controller

We will create a Spring REST Controller class that will expose an endpoint on http/https port. This endpoint returns the temperature after conversion into farenheit from celsius or vice versa.

package com.roytuts.spring.rest.https.rest.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.roytuts.spring.rest.https.dto.Temperature;
import com.roytuts.spring.rest.https.enums.TemperatureUnit;

@RestController
public class TempConverterRestController {

	@PostMapping("/temperature")
	public ResponseEntity<Temperature> getTemperature(@RequestBody Temperature temperature) {
		Temperature temp = new Temperature();

		if (TemperatureUnit.CELSIUS == temperature.getUnit()) {
			double fahrenheit = (9.0 / 5.0) * temperature.getValue() + 32;

			temp.setValue(fahrenheit);
			temp.setUnit(TemperatureUnit.FAHRENHEIT);
		} else if (TemperatureUnit.FAHRENHEIT == temperature.getUnit()) {
			double celsius = (5.0 / 9.0) * (temperature.getValue() - 32);

			temp.setValue(celsius);
			temp.setUnit(TemperatureUnit.CELSIUS);
		} else {
			throw new IllegalArgumentException("Illegal Argument");
		}

		return new ResponseEntity<Temperature>(temp, HttpStatus.OK);
	}

}

The enum and DTO class which were used in the above Spring REST controller class are given below.

package com.roytuts.spring.rest.https.enums;

public enum TemperatureUnit {

	CELSIUS, FAHRENHEIT;

}
package com.roytuts.spring.rest.https.dto;

import com.roytuts.spring.rest.https.enums.TemperatureUnit;

public class Temperature {

	private double value;
	private TemperatureUnit unit;
        
        //getters and setters

}

Main Class

We need a class having main method with @SpringBootApplication annotation to deploy the application into embedded Tomcat server.

package com.roytuts.spring.rest.https;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringRestHttpsServerApp {

	public static void main(String[] args) {
		SpringApplication.run(SpringRestHttpsServerApp.class, args);
	}

}

REST Client

This the client project using Spring Boot framework which will call the REST API endpoint on http/https port.

Create Project

Here also the first step is to create a project in Eclipse. The name of the project is spring-rest-https-server.

If you are creating a gradle based project then you can use the below build.gradle script. We have excluded the Tomcat server from the starter-web dependency as we don’t need to start the server to call the REST service using Spring RestTemplate API.

buildscript {
	ext {
		springBootVersion = '2.2.4.RELEASE'
	}
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") {
    	exclude group:'org.springframework.boot', module:'spring-boot-starter-tomcat'
    }
}

If you are creating maven based project then you can use the below pom.xml file.

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-rest-https-client</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

    <build>
        <plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>at least 8</source>
					<target>at least 8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

REST Template

As we said that we will use Spring RestTemplate to invoke the REST service to get the desired result. Therefore we are going to create a client class with the below source code.

package com.roytuts.spring.rest.https.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.roytuts.spring.rest.https.dto.Temperature;
import com.roytuts.spring.rest.https.enums.TemperatureUnit;

@Component
public class TempConverterRestClient {

	@Autowired
	private RestTemplate restTemplate;

	public void temperature() {
		Temperature temperature = new Temperature();
		temperature.setValue(32.0);
		temperature.setUnit(TemperatureUnit.CELSIUS);

		final String url = "http://localhost:8080/temperature";

		Temperature temp = restTemplate.postForObject(url, temperature, Temperature.class);

		System.out.println(temp.getValue() + " " + temp.getUnit().name());

		temperature = new Temperature();
		temperature.setValue(89.6);
		temperature.setUnit(TemperatureUnit.FAHRENHEIT);

		temp = restTemplate.postForObject(url, temperature, Temperature.class);

		System.out.println(temp.getValue() + " " + temp.getUnit().name());
	}

}

RestTemplate Config

As we have used RestTemplate in our above client class, so we need to configure the RestTemplate as a bean.

package com.roytuts.spring.rest.https.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder builder) {
		return builder.build();
	}
	
}

Note that we have the same enum and DTO class in our client application.

Main Class

We need a class having main method with @SpringBootApplication annotation to run our application.

In this client application we are running the application as a standalone or non web application. Therefore we implement the interface CommandLineRunner.

package com.roytuts.spring.rest.https;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.roytuts.spring.rest.https.client.TempConverterRestClient;

@SpringBootApplication
public class SpringRestHttpsClientApp implements CommandLineRunner {

	@Autowired
	private TempConverterRestClient tempConverterRestClient;

	public static void main(String[] args) {
		SpringApplication.run(SpringRestHttpsClientApp.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		tempConverterRestClient.temperature();
	}

}

Testing the Application

Make sure you run the server application followed by client application.

On client application console you will get the below output:

89.6 FAHRENHEIT
32.0 CELSIUS

Server Certificate

So far so good… We were able to run our applications over http protocol. So the data passed over the network was not secured. Therefore I am going to tell you how to use https protocol (SSL connection) and authenticate using client certificate for establishing communication between server and client applications.

Related Posts:

Now to make our applications runnable on https protocol using certificate, we need certificate. We will generate our own SSL certificate using Java’s built-in keytool utility. In other cases you will get such certificate from certificate authority.

If you have already setup environment variable for Java then you may be able to generate from any path location or you may navigate to the jdk bin directory from cmd prompt and execute the following command. Please note that password must be at least six characters long in length.

C:\Java\jdk-12.0.2\bin\keytool -genkey -keyalg RSA -alias selfsigned -keystore certificate.jks -storepass changeit -validity 360

When you type the above command you will be asked few questions and you may answer them similar to as shown in the below image:

REST over Https with Client certificate Authentication

So you have successfully generated a keystore called certificate.jks with a newly generated certificate in it with certificate alias selfsigned and password changeit and validity for this certificate is 360 days.

Note your certificate is generated under the directory as you are executing the command currently on a directory in the cmd line tool.

Check the generated certificate using the following command:

C:\Java\jdk-12.0.2\bin\keytool -list -keystore certificate.jks -storepass changeit

You will find similar to the below image:

REST over Https with Client certificate Authentication

Then we use this certificate in our server application by declaring the followings in the application.properties file in src/main/resources directory.

server.port=8443
server.ssl.key-store=classpath:certificate.jks
server.ssl.key-store-password=changeit
server.ssl.key-alias=selfsigned

Now you see we have updated default port from 8080 to 8443 to use https instead of http protocol.

Please make sure you have put the certificate.jks file under classpath src/main/resources directory.

Testing the Application

Now run the server application.

Next replace the line in class TempConverterRestClient in client application.

final String url = "http://localhost:8080/temperature";

by

final String url = "https://localhost:8443/temperature";

Now run the client application, you will get error similar to below (full stack trace removed):

Bad Request
This combination of host and port requires TLS

Client Certificate

Now we will create client certificate to handshake with server application.

Let’s create separate certificate for client. Here we will access the service from Java code, so we will create client certificate for Java client.

If you access the service from other clients as well, then create certificate for each client you are accessing from.

Use the following command in cmd prompt in order to generate client certificate for Java client:

C:\Java\jdk-12.0.2\bin\keytool -genkey -keyalg RSA -alias javaclient -keystore javaclient.jks -storepass changeit -validity 360

So when prompt for several questions then give the same answers you had give while generating the server certificate.

REST over Https with Client certificate Authentication

Now we need to extract the certificate from truststore for Java client because we need to import this certificate for remote authentication using the following command:

C:\Java\jdk-12.0.2\bin\keytool -export -alias javaclient -file javaclient.crt -keystore javaclient.jks -storepass changeit

So the certificate file javaclient.crt gets generated.

REST over Https with Client certificate Authentication

Now we have to add the above generated certificate to keystore in order to establish the handshake between client and server.

C:\Java\jdk-12.0.2\bin\keytool -import -alias javaclient -file javaclient.crt -keystore truststore.jks -storepass changeit

Once prompted for Trust this certificate? [no]: . Type yes.

REST over Https with Client certificate Authentication

Now check the truststore should have javaclient certificate using below command:

C:\Java\jdk-12.0.2\bin\keytool -list -keystore truststore.jks -storepass changeit

So you will see output something similar to the below image:

Configure Server Application (REST Server)

Now we need to configure also javaclient truststore at server side so that server knows who is trying to establish connection among themselves.

So put the generated truststore.jks in the classpath directory src/main/resources.

So the whole application.properties file looks similar to below:

server.port=8443
server.ssl.key-store=classpath:certificate.jks
server.ssl.key-store-password=changeit
server.ssl.key-alias=selfsigned
server.ssl.trust-store=classpath:truststore.jks
server.ssl.trust-store-password=changeit
server.ssl.client-auth=need

It is mandatory to set the server.ssl.client-auth=need in order to make the client authentication mandatory.

So now you neither be able to view nor be able to connect to service from anywhere except Java client.

Configure Client Application (REST Client)

Next put the generated javaclient.jks (remember you generated this file during generating truststore) file under classpath directory src/main/resources. Put also the certificate.jks file (generated at the server side code) under classpath directory src/main/resources directory.

We need Apache httpclient library to produce the socket factory for establishing the TLS/SSL connection. Now we will update the RestTemplate bean to have a supplier of request factory instance.

Therefore first make an entry to the build.gradle or pom.xml file:

implementation("org.apache.httpcomponents:httpclient:4.5.11")

or

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.5.11</version>
</dependency>

Next we need to update the RestTemplate bean as given below:

package com.roytuts.spring.rest.https.config;

import java.io.InputStream;
import java.security.KeyStore;
import java.util.function.Supplier;

import javax.net.ssl.SSLContext;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder builder) {
		return builder.requestFactory(myRequestFactorySupplier()).build();
	}

	@Bean
	public MyRequestFactorySupplier myRequestFactorySupplier() {
		return new MyRequestFactorySupplier();
	}

	class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

		@Override
		public ClientHttpRequestFactory get() {
			HttpComponentsClientHttpRequestFactory requestFactory = null;
			try {
				char[] password = "changeit".toCharArray();
				KeyStore ks = KeyStore.getInstance("JKS");
				InputStream ksStream = this.getClass().getClassLoader().getResourceAsStream("javaclient.jks");
				ks.load(ksStream, password);

				SSLContext sslContext = new SSLContextBuilder()
						.loadTrustMaterial(this.getClass().getClassLoader().getResource("certificate.jks"), password)
						.loadKeyMaterial(ks, password).build();

				SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
						NoopHostnameVerifier.INSTANCE);

				CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLSocketFactory(socketFactory).build();

				requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
				requestFactory.setBufferRequestBody(false);
			} catch (Exception e) {
			}

			return requestFactory;
		}

	}
}

Testing the Application

Finally run the server application followed by client application. Eventually you will get the same output over https protocol as you got over http protocol.

89.6 FAHRENHEIT
32.0 CELSIUS

That’s all. Hope you got an idea how to authenticate client application using certificate.

Source Code

Download

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *