Spring Boot SSL [https] Integration

In this article, we will learn to configure SSL (HTTPS) in a Spring Boot application using a self-signed certificate. For enabling SSL of a  Spring Boot application on the embedded Tomcat, We need to follow these steps.

  1. Create a SSL Certificate: Generate a self-signed certificate or you can use a certificate issued by a trusted Certificate Authority (CA).
  2. Enable HTTPS in Spring Boot: This can be done with some simple configurations in our Spring Boot project.
  3. Redirect HTTP to HTTPS : In some cases it might be a good idea to make your application accessible over HTTP too, but redirect all HTTP  traffic to HTTPS endpoint.

We can generate a Self Signed SSL Certificate ourselves for development and testing purposes. In production, we should use a certificate issued by a trusted Certificate Authority (CA).

Otherwise it will display a warning to the user that your certificate is not trusted.

Generate a Self - Signed SSL Certificate:

Every Java Runtime Environment (JRE) comes bundled with a certificate management utility named keytool. This can be used to generate the self-signed certificate. The two most common formats used for keystores are JKS, a proprietary format specific for Java, and PKCS12, an industry standard format. JKS used to be the default choice, but now Oracle recommends to adopt the PKCS12 format.

Open Terminal prompt and write the following command to create a JKS keystore :

keytool -genkeypair -alias https-integration -keyalg RSA -keysize 2048 
-keystore keystore.jks -validity 3650

To create a PKCS12 keystore, use the following command :

keytool -genkeypair -alias https-integration -keyalg RSA -keysize 2048 
-storetype PKCS12 -keystore keystore.p12 -validity 3650

Enter a password for the keystore. It must have at least of 6 characters.

Enter keystore password:

Re-enter new password:

Finally, we need to provide some input information, but we can skip all of it (just press Enter to skip an option). In the place of the first and last name, we may want to insert the base name of our host like localhost. This may be important some time as the SSL certificate should have correct CN . Common Name (CN) / The Common Name (CN) should be the fully qualified domain name of the Web server that will receive the certificate. Otherwise we can get SSL Exception CertificateException: No name matching <your domain name>  found.Check this for detail of this exception.

Enter keystore password:  [email protected]
Re-enter new password: [email protected]
What is your first and last name?
  [Unknown]:  localhost
What is the name of your organizational unit?
  [Unknown]:  Engineering
What is the name of your organization?
  [Unknown]:  Tuturself
What is the name of your City or Locality?
  [Unknown]:  Pune
What is the name of your State or Province?
  [Unknown]:  Maharashtra
What is the two-letter country code for this unit?
  [Unknown]:  IN
Is CN=localhost, OU=Engineering, O=Tuturself, L=Pune, ST=Maharashtra, C=IN correct?
  [no]:  yes

At the end of this operation, we’ll get a keystore containing a brand new SSL certificate. To check the content of the JKS keystore, we can use keytool again:

keytool -list -v -keystore keystore.jks

To test the content of a keystore following the PKCS12 format, we can use the following command:

keytool -list -v -storetype pkcs12 -keystore keystore.p12

Enable HTTPS in Gateway Project:

For both keystore contains either a self-signed certificate or one issued by a trusted Certificate Authority, we can now set up the Spring Boot Project to accept requests over HTTPS instead of HTTP by using that certificate. The first thing to do is placing the keystore file inside the Spring Boot project. We may put it in the resources folder or  inside the root folder. Now we need to put the following configurations in application.properties file / application.yaml / bootstrap.yml file.

spring:
  application:
    name: spring-test-service

# SSL Configurations STARTS Here
server:
  port: 8443
  http-port: 8099
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: [email protected]
    keyStoreType: PKCS12
    keyAlias: https-integration
# SSL Configurations ENDS Here

logging:
  level:
    org.springframework: INFO
    com.qualys: INFO
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file: /tmp/logs/https-integration-application.log

We can specify a custom port using the server.port property (by default it is 8080). If we have Spring Security in our class path then we should set the security.require-ssl property to true to automatically block any requests coming from HTTP, without explicitly touching any Spring Security configuration class.

  • The server.ssl.key-store-type property defines the format used for the keystore : It can be either JKS or PKCS12
  • The server.ssl.key-store property contains the path to the keystore file : It can be either keystore.jks or keystore.p12.Here we want Spring Boot to look for this file in the Class path.

Redirect HTTP requests to HTTPS:

In some cases it might be a good idea to make your application accessible over HTTP too, but redirect all HTTP traffic to HTTPS. To achieve this we’ll need to add a second Tomcat connector, but currently it is not possible to configure two connector in the application.properties like mentioned before. Because of this we’ll add the HTTP connector programmatically and make sure it redirects all traffic to our HTTPS connector. For this we will need to add the  @Configuration class.

package com.tuturself.httpsintegration;

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.apache.tomcat.websocket.server.WsSci;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.SpringServletContainerInitializer;

@Slf4j
@Configuration
public class TomcatEmbed extends SpringServletContainerInitializer {

	@Value("${server.port}")
	private Integer httpsPort;

	@Value("${server.http-port}")
	private Integer httpPort;

	@Bean
	public TomcatServletWebServerFactory servletContainer() {
		TomcatServletWebServerFactory tomcat = 
                           new TomcatServletWebServerFactory() {
			protected void postProcessContext(Context context) {
				SecurityConstraint securityConstraint = new SecurityConstraint();
				securityConstraint.setUserConstraint("CONFIDENTIAL");
				SecurityCollection collection = new SecurityCollection();
				collection.addPattern("/*");
				securityConstraint.addCollection(collection);
				context.addConstraint(securityConstraint);
			}
		};
		tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
		return tomcat;
	}

	private Connector initiateHttpConnector() {
		Connector connector = 
                 new Connector("org.apache.coyote.http11.Http11NioProtocol");
		connector.setScheme("http");
		connector.setPort(httpPort);
		connector.setSecure(false);
		connector.setRedirectPort(httpsPort);
		return connector;
	}

	/**
	 * The following code is required only if you WebSocket endpoint
	 * in your application. We added it for some later example. You
	 * can omit this part if you don't have any WebSocket endpoint.
	 */
	@Bean
	public TomcatContextCustomizer tomcatContextCustomizer() {
		log.info("TOMCAT CONTEXT CUSTOMIZER INITIALIZED");
		return new TomcatContextCustomizer() {
			@Override
			public void customize(Context context) {
				context.addServletContainerInitializer(new WsSci(), null);
			}
		};
	}
}

Distribute the SSL Certificate to Clients:

As we are using a self-signed SSL certificate, the browser won’t trust our application and will warn the user that it’s not secure domain. And that’ll be the same for all types of client. It’s possible to make a client to trust the application by providing it with the certificate. Since we stored the certificate inside the keystore, we need to extract it by the keytool for JKS format:

keytool -export -keystore keystore.jks -alias tomcat -file myCertificate.crt

Now the keytool will ask for the keystore password that we have set at the beginning of this guide ([email protected]). Now we can import the certificate into the client. In the next step Import the certificate inside the JRE keystore, we’ll explain how to import the certificate of JKS format into the JRE.

On the other hand, if we are using a keystore in PKCS12 format, we should be able to use it directly without extracting the certificate.Please check the guide on how to import a PKCS12 file into your browser. If you’re on macOS, you can directly import your certificate into the Keychain Access (which browsers like Safari, Chrome and Opera rely on to manage certificates).

If we are going to deploy the application on localhost, we may need to do a further step from browser: enabling insecure connections with localhost. In Chrome, we can do that by writing the following URL in the search bar: chrome://flags/#allow-insecure-localhost and activating the relative option.

Import the certificate inside the JRE keystore:

To make the JRE to trust our certificate, we need to import it inside cacerts: the JRE keystore in charge of holding certificates. From Terminal prompt insert the following command (We need to run the terminal with administrator privileges):

keytool -importcert -file myCertificate.crt -alias tomcat -keystore 
  C:/Program\ Files/Java/jdk1.8.0_121/jre/lib/security/cacerts

This will ask to input the JRE keystore password. If you have never changed it, it should be the default one: changeit or changeme, depending on the Operating System. Finally, keytool will ask if we want to trust this certificate: say yes. Now we can see the message as Certificate was added to keystore. Now the application can accept both HTTP and HTTPS requests. But all HTTP calls will be redirected to HTTPS endpoint. In this application we have created a Employee Service where we can search for an Employee by Id or can view all the Employees present in Database.

Creating the Domain Model

Let’s create our domain model - Employee. Create a file named Employee.java with the following contents –

package com.tuturself.httpsintegration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Data
@AllArgsConstructor
@NoArgsConstructor
class Employee {

	private UUID employeeId;
	private String name;
}

The @Data @AllArgsConstructor @NoArgsConstructor are from project lombork, which will provide the getters, setters and Constructors etc.

Creating the Repository

Next, we’re going to create the data access layer which will be used to access the database. The reactive repository is created in a file called EmployeeRepository.java with the following contents. This will have some dummy Employee data.

package com.tuturself.httpsintegration;

import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Repository
class EmployeeRepository {

	List<Employee> employees = new ArrayList<>();

	public EmployeeRepository() {
		employees.add(new Employee(UUID.randomUUID(), "Robert Barnes"));
		employees.add(new Employee(UUID.randomUUID(), "Robin Bennett"));
		employees.add(new Employee(UUID.randomUUID(), "Harvey Berg"));
		employees.add(new Employee(UUID.randomUUID(), "Joanne Dalton"));
		employees.add(new Employee(UUID.randomUUID(), "Keifer Davey"));
		employees.add(new Employee(UUID.randomUUID(), "Grace Dobson"));
	}

	public Flux<Employee> findAll() {
		return Flux.fromStream(employees.stream());
	}

	public Mono<Employee> findById(String id) {
		UUID empId = UUID.fromString(id);
		return Mono.justOrEmpty(employees.stream().
				filter(e -> 
                e.getEmployeeId().equals(empId)).findFirst());
	}
}

Creating the Controller Endpoints

Finally, Let’s write the APIs that will be exposed to the clients. Create a new file called WebService.java with the following contents.

package com.tuturself.httpsintegration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class WebService {

	@Autowired
	private EmployeeRepository repository;

	@GetMapping("/employees")
	public Flux<Employee> getAllEmployees() {
		return repository.findAll();
	}

	@GetMapping("/employees/{id}")
	public Mono<ResponseEntity<Employee>> getAllEmployees(@PathVariable("id") 
                         String employeeId) {
		return repository.findById(employeeId)
				.map(employee -> ResponseEntity.ok(employee))
				.defaultIfEmpty(ResponseEntity.notFound().build());
	}
}

All the controller endpoints return a Publisher in the form of a Flux or a Mono.Now that we have publishers as Controller endpoints which can produce a stream of events. Consider the following endpoint.

@GetMapping("/employees")
public Flux<Employee> getAllEmployees() {
  return repository.findAll();
}

Flux<Employee> represents a stream of employees. But, by default, it will produce a JSON array because If a stream of individual JSON objects is sent to the browser then It will not be a valid JSON document as a whole. A browser client has no way to consume a stream other than using Server-Sent-Events or WebSocket. However, Non-browser clients can request a stream of JSON by setting the Accept header to application/stream+json, and the response will be a stream of JSON similar to Server-Sent-Events but without extra formatting :

Make the Application Executable

Following is our Main application class, which will make the application executable.

package com.tuturself.httpsintegration;

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

@SpringBootApplication
public class HttpsIntegrationApplication {

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

@SpringBootApplication is a convenience annotation that adds all of the following:

  • @Configuration tags the class as a source of bean definitions for the application context.

  • @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.

  • Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.

  • @ComponentScan tells Spring to look for other components, configurations, and services in the httpsintegration package, allowing it to find the controllers.

The main() method uses Spring Boot’s SpringApplication.run() method to launch an application.

Now we can test the exposed url by the following URLs.

HTTPS URL   : https://localhost:8443/employees

Example O/P : 

// 20180719002420
// https://localhost:8443/employees

[
  {
    "employeeId": "ae20c4bd-95c4-414e-8814-b118ec81b3d8",
    "name": "Robert Barnes"
  },
  {
    "employeeId": "f81233a2-86ac-4a38-9c45-d3619c03463d",
    "name": "Robin Bennett"
  },
  {
    "employeeId": "ef6658f7-2d20-4af5-8136-e3e4a5157788",
    "name": "Harvey Berg"
  },
  {
    "employeeId": "be5a578b-eecd-4bb7-97bf-51c4a58d6996",
    "name": "Joanne Dalton"
  },
  {
    "employeeId": "46e52c52-1d23-437d-9def-04c13722ad50",
    "name": "Keifer Davey"
  },
  {
    "employeeId": "be7b3320-77c3-43be-9c7e-d5d4fa42e324",
    "name": "Grace Dobson"
  }
]

HTTPS URL   : https://localhost:8443/employees/ae20c4bd-95c4-414e-8814-b118ec81b3d8

Example O/P : 

// 20180719002542
// https://localhost:8443/employees/ae20c4bd-95c4-414e-8814-b118ec81b3d8

{
  "employeeId": "ae20c4bd-95c4-414e-8814-b118ec81b3d8",
  "name": "Robert Barnes"
}

You can also make a call to URL http://localhost:8099/employees or http://localhost:8099/employees/{emp_id} , which will be automatically redirect the the HTTPS endpoint.

Download the Source Code from here :: GITHUB

SPRING WEB-DEVELOPMENT SPRING BOOT