Project Archive

Some additional features of Spring Boot

In the first Spring Boot example I posted to the course web site I limited myself to only the most basic features of Spring Boot, since I wanted us to be able to implement only the most essential server features first. In these lecture notes I am going to cover some additional "nice to have" features of Spring Boot.

At the top of these lecture notes you will find a button you can click to download an updated version of the example project.

HTTP error codes

The clients who communicate with our server will use the HTTP protocol. One of the key features of HTTP is the use of status codes. When a client sends a request the server will respond with a status code and data in the body of the response. In the case of an unsuccessful request the status code is meant to give the client some feedback on what went wrong. Also, most servers will put a more detailed error message in the body of the response if the request is not successful.

Below is an example of some Spring Boot controller code that demonstrates how to set a status code and an error message when something goes wrong.

@GetMapping("/login")
public ResponseEntity<String> checkLogin(@RequestParam(value = "name") String user, @RequestParam(value = "password") String password) {
    User result = dao.findByNameAndPassword(user, password);
    if (result == null) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid user name or password");
    }
    return ResponseEntity.ok().body(result.getKey());
}

The method here is meant to handle a login process. The client will provide a user name and a password. The dao method that this method calls to check the login is set up to return null if the user name or password are not valid.

To handle error situations we need to both set a response code and provide a custom body with an error message. To do this we make use of a ResponseEntity object. The first step in using a ResponseEntity is to change the return type of the controller method to return a ResponseEntity instead of a regular data type. The ResponseEntity acts as a container for both the status code and the body (and even additional elements such as response headers should you need them). To construct a ResponseEntity we use a builder pattern: we start by calling a static method in the ResponseEntity class to start the process, and then call additional methods on the object returned by that first method call until we have completed the process of building the object. In this case we start by calling the status() method to set a status code in the response, and then follow that with a call to the body() method to specify a body for the request. An important restriction to pay attention to with the body() method is that the type of the parameter you pass to the body() method much match the type in the angle brackets in the ResponseEntity.

We specify which status code to use by using a constant defined in the HttpStatus enumerated type. There are a large number of status codes available to choose from in Http. This page provides a list of those status codes.

Validation and other error checking

Another type of error checking involves checking inputs that come from users to make sure that they are valid. This process of error checking is generically known as validation.

Here is a typical example of validation. Below is code for a method to handle posting new users.

@PostMapping
public ResponseEntity<String> save(@RequestBody User user) {
    if (user.getName().isBlank() || user.getPassword().isBlank()) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Empty user name or password");
    }

    String key = dao.save(user);
    if (key.equals("Duplicate")) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body("User with this name already exists");
    } else if (key.equals("Error")) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Can not generate key");
    }
    return ResponseEntity.ok().body(key);
}

An obvious validation check that needs to be performed here is checking whether or not the provided user name or password in the User object are empty.

Validation can catch obvious errors, such as missing data or data that has the wrong format. Other errors will require more careful checking. In this example the validation check can find missing names or passwords. Additional error checking is handled by the DAO: for example, before inserting a new user into the table of users the DAO will check to see if a user with the requested name already exists. If that is the case, the DAO method will return "Duplicate". The code in the controller method will simply have to check the value returned by the DAO method to see if any errors were caught at that level of the processing.

Improving security with password hashing

In the first version of the Spring Boot application I presented we followed a very simple strategy to handle passwords. When we stored a new User in the database we would simply store that user's password as a field in the users table. Unfortunately, this simple approach is considered insecure. The problem with this approach is that if anyone ever breaks into your database they can steal all of your user name/password combinations and also get users' email addresses from the profile information in your database. This is a problem because many users reuse user name/password/email combinations across multiple sites, so a security breach in your site indirectly becomes a security breach in a bunch of other sites.

The standard fix for this security problem is to encrypt passwords stored in a database. In the updated version of the application I am going to make use of a strategy for handling passwords securely that is widely followed in the real world: the practice of storing salted and hashed passwords in the database.

This security practice starts with the use of a special function called a hash function. A hash function is a crytographic algorithm that takes a string as its input and maps that string to a very large integer and then subsequently recodes that integer as a string. What is special about hash functions is that they are designed to be one-way functions: given an input string the hash function produces an output string, but there is for all practical purposes no way to reverse the mapping and recover the original input string.

To make this process even more secure, programmers typically combine the use of a hash function with an additional step called salting. In this version of the process we take the input string and combine it with a secret value called a salt value before running the input through the hash function. The motivation for the extra step is that it can help prevent what is commonly referred to as a dictionary attack. In a dictionary attack an adversary starts by guessing what hash function you are using and then prepares a long list of common passwords. They then compute hashes for each of these passwords and store the combinations of the passwords and the hashes in a dictionary. If that adversary subsequently steals your database of hashed passwords they can then recover the original, unhashed passwords for many of your users by doing a reverse lookup in their dictionary. Salting passwords before hashing them shuts down this attack. If the adversary does not know what value you are using to salt the passwords before hashing them they can not use the dictionary attack.

To implement our password security process in the auction application I start by constructing a special class that contains methods to help us to hash and verify passwords:

import org.springframework.stereotype.Service;

import com.password4j.BcryptFunction;
import com.password4j.Hash;
import com.password4j.Password;
import com.password4j.types.Bcrypt;

@Service
public class PasswordService {
  
  static private final String secret="CMSC455";
  
  public String hashPassword(String password) {
    BcryptFunction bcrypt = BcryptFunction.getInstance(Bcrypt.B, 12);

    Hash hash = Password.hash(password)
                        .addPepper(secret)
                        .with(bcrypt);

    return hash.getResult();
  }
  
  public boolean verifyHash(String password,String hash) {
    BcryptFunction bcrypt = BcryptFunction.getInstance(Bcrypt.B, 12);

    return Password.check(password, hash)
                   .addPepper(secret)
                   .with(bcrypt);
  }
}

This code makes use of a popular crytographic library for Java called password4j. To use the library in my project I added a dependency to the pom.xml file

<dependency>
    <groupId>com.password4j</groupId>
    <artifactId>password4j</artifactId>
    <version>1.6.1</version>
</dependency>

and then imported some special purpose classes from that library. The password4j library offers a variety of hash functions: for this example I am using the Bcrypt hash function. The Password class from password4j offers easy to use hash() and check() functions to hash and verify hashed passwords.

I put these two utility methods into a service class. To make a service class in Spring Boot we add the special @Service annotation in front of the class declaration. Attaching this annotation to the class causes Spring to automatically create an instance of this class when our application starts up. That object can then be injected into any other class in our system that needs it.

The code below demonstrates how I use this new service class in the UserDAO class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import edu.lawrence.auction.services.PasswordService;

@Repository
public class UserDAO {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired 
    PasswordService passwordService;
    
    public User findByNameAndPassword(String name,String password) {
      String sql = "SELECT * FROM users WHERE name=?";
        RowMapper<User> rowMapper = new UserRowMapper();
        User result = null;
        try {
            result = jdbcTemplate.queryForObject(sql, rowMapper, name);
        } catch(Exception ex) {
            
        }
        if(result != null && passwordService.verifyHash(password, result.getPassword())) {
          result.setPassword("Undisclosed");
        } else {
          result = null;
        }
        return result;  
    }

    public String save(User user) {
        // First make sure this is not a duplicate
        String sql = "SELECT * FROM users WHERE name=?";
        RowMapper<User> rowMapper = new UserRowMapper();
        User old = null;
        try {
            old = jdbcTemplate.queryForObject(sql, rowMapper, user.getName());
        } catch(Exception ex) {

        }
        if(old != null)
            return "Duplicate";

        // Have MySQL generate a unique id
        String idSQL = "select uuid()";
        String key = null;
        try {
            key = jdbcTemplate.queryForObject(idSQL, String.class);
        } catch(Exception ex) {
            key = "Error";
        }
        if(key.equals("Error"))
            return key;

        String hash = passwordService.hashPassword(user.getPassword());
        String insertSQL = "insert into users(userid,name,password) values (?, ?, ?)";
        jdbcTemplate.update(insertSQL,key,user.getName(),hash);
        return key;
    }
}

To automatically inject an instance of the PasswordService class into the DAO I simply needed to declare a member variable of the appropriate type in the DAO class and annotate that member variable with the @Autowired annotation.

In the code for storing users in the database and the code for checking user name/password combinations you can now see me using the password service class to encrypt passwords before storing them in the database and then verify that candidate passwords match what is in the database.

Deployment

For any real-world project we construct we will eventually have to come up with a deployment strategy that allows us to make our server accessible on the internet. To demonstrate how easy it is to deploy a Spring Boot project I have gone ahead and deployed this project on a server I control.

The server is a modest Mac Mini computer that I have set up at my home. I also registered a domain, cmsc106.net, and set up a DNS entry to point that domain to my home network.

On the Mac Mini I started by using homebrew to install the Apache we server. To deploy the application I also used homebrew to install the Tomcat application server, and I then also configured the Apache server to forward all requests to cmsc106.net to the Tomcat server.

To prepare my project for deployment to the Tomcat server I made two small changes to the project.

The first change was to make a small adjustment to the application class in my project. I changed

public class AuctionApplication {

to

public class AuctionApplication extends SpringBootServletInitializer {

The second change was to add the line

<packaging>war</packaging>

to the pom.xml file. Normally, when you compile a Spring Boot project the build system will produce an executable jar file as the final project. If you want to deploy your project to an existing Tomcat server you put this line in the pom.xml file to tell the system to generate a war file instead of a jar file. Once the war file is built, all you have to do is to drop that war file into the webapps folder in the Tomcat folder on the server.

You can see all of this in action with Postman: just set up POST request with the URL

http://cmsc106.net/auction/users

to post an object with a name/password combination. If your post is successful, the server will respond by sending you back a user id for the new user you created.

Documenting our API

We have now constructed our first simple Spring Boot application that provides a simple REST API to enable users to create new users and to log in to the system by providing a user name and a password. In the world of software development whenever we build a product that offers a set of capabilities to users we are generally obligated to provide potential users with documentation to explain how to use our system. This requirement is only going to grow as we add more and more features to our application's API.

Another feature that I have added to this example project involves the use of the Springdoc library. To add this library to the project I started by adding a dependency in the pom.xml file:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>

As soon as we add this library to our project we will get access to a powerful new feature almost for free. You can see this new feature in operation in the copy of the project I deployed to the cmsc106.net server. Just enter the URL

http://cmsc106.net/auction/swagger-ui/index.html

in a browser to see the documentation page that springdoc generated for my project. This documentation page documents both of the interactions provided by the current UI. All of this was generated for me automatically as soon as I added the dependency to the project. This powerful new capability is only going to grow in usefulness as I build out the API for the server and add lots of new interactions.