loading...
Cover image for Secure a Spring Boot Application with TLS

Secure a Spring Boot Application with TLS

shubhamjadhav profile image shubham jadhav ・5 min read

Security is an important aspect of any application and nearly all production-grade applications employ a certain level of security mechanism for application security. Transport Layer Security allows applications to develop a secure communication channel with its clients. In this article, we will create a Spring boot application and secure it with a self-signed certificate.


Creating a Spring Boot Application

In this section, we will create a Spring boot application and expose the following endpoints:

GET v1/books/ : List all books
POST v1/books/: Create a new book
GET v1/books/{book_id}: Get a book resource
DELETE v1/books/{book_id}: Remove a book

Step 1: Creating a Spring Boot Project

Browse to your favorite IDE and create a Spring boot project with web, h2, data-jpa and Lombok dependencies. Following is the pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.codefountain</groupId>
    <artifactId>secure-rest-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>secure-rest-api</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Configuring h2 database

In this application, we will use the h2 in-memory database as our backing database. Add the following configuration in the application.properties file to configure h2 database:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

Step 3: Creating the domain object

In this application, we will manage book information. Users of this application/API can perform CRUD operations. We have created the following book entity:

package io.codefountain.api.domain;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;

@Data
@Entity
public class Book {

    @Id
    private long bookId;
    private String bookTitle;
    private String bookAuthor;
    private String bookPublisher;

}

Step 4: Creating the REST endpoints

To facilitate the REST endpoints, we have created the following REST controller. It serves the aforementioned endpoints:

package io.codefountain.api.controller;

import io.codefountain.api.domain.Book;
import io.codefountain.api.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/v1/books")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public Iterable<Book> getBooks(){
        return bookRepository.findAll();
    }

    @PostMapping
    public Book createBook(@RequestBody Book book){
        return bookRepository.save(book);
    }

    @GetMapping("/{bookId}")
    public Book getBook(@PathVariable Long bookId){
        return bookRepository.findById(bookId).orElseThrow(RuntimeException::new);
    }

    @DeleteMapping
    public void deleteBook(@PathVariable Long bookId){
        bookRepository.deleteById(bookId);
    }
}

We also have created the following repository:

package io.codefountain.api.repository;

import io.codefountain.api.domain.Book;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {
}

Step 5: Insert data at application startup

To help with the testing, let's insert a few book details in our database. To do so, create a new SQL file named data.sql in src/main/resources directory. Spring boot automatically executes this file at startup.

insert into book(book_Id, book_Title, book_Author, book_Publisher) values(1, 'Java in Action', 'Some Author', 'Pearson');
insert into book(book_Id, book_Title, book_Author, book_Publisher) values(2, 'Java Primer', 'Some Author', 'Pearson');
insert into book(book_Id, book_Title, book_Author, book_Publisher) values(3, 'Spring in Action', 'Some Author', 'Pearson');
insert into book(book_Id, book_Title, book_Author, book_Publisher) values(4, 'JavaScript Primer', 'Some Author', 'Pearson');
insert into book(book_Id, book_Title, book_Author, book_Publisher) values(5, 'Scala in Action', 'Some Author', 'Pearson');

Step 6: Testing the API

Let us now start the application and test a few endpoints to ensure the application is working as expected. We are using HTTPPie to access the endpoint and we are receiving data from the server as shown below:

http http://localhost:8080/v1/books/1
HTTP/1.1 200
Content-Type: application/json
Date: Mon, 02 Dec 2019 05:58:30 GMT
Transfer-Encoding: chunked

{
"bookAuthor": "Some Author",
"bookId": 1,
"bookPublisher": "Pearson",
"bookTitle": "Java in Action"
}

Configuring a Self-Signed Certificate

So far, we have developed the application and able to access the configured endpoints. At the moment, this application is running on an insecure HTTP protocol. In this section, we will enable TLS and let the application run in HTTPS.

In order to do so, we need to configure a certificate. In production-grade applications, we use certificates that are issued from renowned Certification Authorities (CA) such as Verisign, DigiCert, Entrust and others. Certificates issued by renowned CAs ensure that our application is a trusted entity. However, as this is a demo application, we will create a Self-Signed Certificate and use it in our application.

Step 1: Creating the certificate

Java provides the keytool utility to create and manage certificates locally. Keytool is available along with other JDK utilities in JDK_HOME/bin directory. Let us execute the following keytool command to generate a new certificate:

keytool -genkey -keyalg RSA -alias max -keystore max.jks -storepass password -validity 365 -keysize 4096 -storetype pkcs12

We are generating a certificate along with the following tasks in the above command:

  • Using the RSA algorithm
  • Providing an alias name as max
  • Naming the Keystore file as max.jks
  • Validity for one year

Once you hit this command, it will prompt for a few details and the certificate will be created. Copy this certificate in the src/main/resources directory so that it will be available at classpath.

Step 2: Configuring the application with TLS

Now that we are done with the certificate generation, let us add the following information in the Spring boot application.properties to enable TLS:

server.ssl.key-store=classpath:max.jks
server.ssl.key-store-type=pkcs12
server.ssl.key-store-password=password
server.ssl.key-password=password
server.ssl.key-alias=max
server.port=8443

Step 3: Testing the API

We have added the TLS configuration in Spring boot and the application is ready to run in HTTPS. Open Postman/Browser and hit the below URL:
https://localhost:8443/v1/books/1
In postman, we find the following output:

Alt Text

Many HTTP clients have a check on the Self-Signed certificates and it does not allow accessing resources over Self-Signed certificates HTTPS. For example, HTTPPie returns the following error. Ensure to disable the check to test this demo application.

http https://localhost:8443/v1/books/1
http: error: SSLError: HTTPSConnectionPool(host=localhost, port=8443): Max retries exceeded with url: /v1/books/1 (Caused by SSLError(SSLCertVerificationError(1, [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1076)))) while doing GET request to URL: https://localhost:8443/v1/books/1

Wrapping Up

Enabling Transport Layer Security in an application is a defacto standard in today’s security parlance. Moreover, when your application let its clients authenticate over HTTP Basic Authentication scheme, then configuring HTTPS is not a choice, its the default requirement. This article shed some light on how to achieve this in a Spring boot application.

Posted on May 30 by:

Discussion

markdown guide