19.3. Creating a User Model

The next few sections walk through the steps necessary to enable simple authentication in the coding-events app. Along the way, we will use some advanced concepts that you haven’t fully learned. That’s okay. We don’t expect you to understand every detail the next few sections. However, you do need to understand the purpose of each step in enabling authentication.

Note

While we’ll use coding-events, these steps would be the same for any other app. If you want to add simple authentication to a Spring Boot application in the future, reference this chapter.

Before we can authenticate users, we need users to authenticate! We’ll start by adding a User model.

19.3.1. A User Model

A model class representing users needs, at a minimum, fields representing username and password.

In the models package, create a User class with @Entity and extending AbstractEntity. It should have two string fields, username and pwHash. We only need a getter for username.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Entity
public class User extends AbstractEntity {

   @NotNull
   private String username;

   @NotNull
   private String pwHash;

   public User() {}

   public User(String username, String password) {
      this.username = username;
      this.pwHash = password;
   }

   public String getUsername() {
      return username;
   }

}

Notice that the constructor takes a parameter named password and uses it to set the value of pwHash. We mentioned previously that we should never store passwords, so in a moment, we will update line 26 by creating a hash from the given password to store.

Note

Our validation annotations on User are very lenient. This is okay, however, because we will validate user input used to make User objects using a DTO with more restrictive validation.

19.3.2. Hashing Passwords

We’ll use the bycrypt hash algorithm. One way to access this algorithm is via the following dependency:

org.springframework.security:spring-security-crypto

Add this as an implementation dependency in your build.gradle file.

This dependency provides the BCryptPasswordEncoder class, which we will use to create and verify hashes. While our class needs one of these encoder objects, it does not need to be an instance variable. We’ll make it static so it can be shared by all User objects.

25
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

In the constructor, we can use encoder to create a hash from the given password:

29
30
31
32
public User(String username, String password) {
   this.username = username;
   this.pwHash = encoder.encode(password);
}

Our User objects should also be responsible for determining if a given password is a match for the hash stored by the object. We can do this using the encoder.matches() method. Let’s put this behavior in a method at the bottom of our User class:

38
39
40
public boolean isMatchingPassword(String password) {
   return encoder.matches(password, pwHash);
}

Warning

Notice that we are using encoder.matches() rather than directly comparing hash values. More explicitly, the following comparison will NOT work to compare hashes generated by bcrypt:

public boolean isMatchingPassword(String password) {
   String candidateHash = encoder.encode(password);
   return candidateHash.equals(pwHash);
}

While our conceptual example in the previous section used direct comparison of hashes—and some hashing techniques allow you to do so—bcrypt does not. This is because bcrypt internally uses a technique called salting, which requires additional steps before comparison. These additional steps are carried out by encoder.matches().

19.3.3. Creating the UserRepository

As usual, we need a repository in order to access User objects stored in the database. This time, however, we add a twist. Create UserRepository in the data package, with the following contents:

 9
10
11
12
13
public interface UserRepository extends CrudRepository<User, Integer> {

   User findByUsername(String username);

}

While our repository extends CrudRepository, it also contains a new method, findByUsername. Based on the method signature, it appears that this method is intended to take a username, and return the given user with that username. Indeed, when our application runs, the UserRepository will have such a method.

Spring allows for additional, custom methods to be added to repository interfaces, as long as they follow some basic naming conventions. These conventions are straightforward to use, and allow you to create additional, more sophisticated query methods. Methods created in this way are called query methods, and their rules are defined in Spring’s documentation.

Note

The code for this section is available in the user-model branch of the coding-events-demo repository.

19.3.4. Check Your Understanding

Question

Why can we not use Java string comparison when evaluating values generated with bcrypt?

  1. bcrypt will never create two matching hashes.
  2. Java does not have a native string comparator method.
  3. Salting adds variance to hashes generated from the same plain text.
  4. Answers a and c.

Question

 9
10
11
12
13
public interface UserRepository extends CrudRepository<User, Integer> {

   User findByUsername(String username);

}

True/False: From the code block above, line 11 is missing a return statement.

  1. True
  2. False