Spring Boot API Security with JWT and Role-Based Authorization

Akhilesh Anand
11 min readJul 26, 2020

--

I started writing this article in the hopes that it helps people who have a vague idea of how Spring works to get up and running with Spring Security. There are many articles explaining the exact same thing I have here, but hey, this is my take on this!

To be clear, if you try and follow along, I will be explaining how you would go about setting up a

  1. User Registration API, where each user is assigned a Role.
  2. User Authentication, where valid users are retuned a JWT Token.
  3. Role-based access to specific API targets by means of providing a valid JWT Token.

JSON Web Token (JWT)

Before we start, If you’re new to web development or have no idea about what JWT is, I think its really important to understand what it is and what it does.

JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret or a public/private key pair using RSA or ECDSA.

Highly recommend you read the rest of where that came from here.

So, what exactly are we solving with JWT?

As the description states, we can securely transmit and store verified and trusted information. We will use this capability to store the user’s role as part of the JWT Token’s payload.

We will generate a JWT Token on the server as soon as the user is able to verify their credentials. When a user wants to hit an API, we will append the token to the request’s header. This will not only inform the server of who is trying to make the request but also the role that will help the API server determine if that person has access to the API or not.

For each API, we will assign which roles are able to access them. If the user’s role matches the roles allowed by that API, the request goes through. Else, we return a 403 Forbidden Request.

Project Setup

If you’ve already had everything set up, just make sure you have added all the requisite dependencies to your project.

You could use notepad to code for all I care (take care of the set-up yourself), but I suggest using an IDE. I personally use IntelliJ IDEA. To create a new project I like to use Spring Initializr.

Go ahead and fill in the project metadata to your preference. Spring Initializr allows you to add some of the dependencies right here. Hit Generate and you are presented with a neat little Maven project bundle that you can open via IntelliJ (Open -> Project Folder -> pom.xml).

The dependencies we will be using are

  1. Spring Web
  2. Spring Data JPA
  3. Spring Security
  4. MySQL Driver
  5. JSON Web Token

Which will look like something like this on your pom.xml

<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

And here is the project structure. I have added several new packages (in bold) that will help us organize our classes better.

├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── akhianand
│ └── springrolejwt
│ ├── SpringRoleJwtApplication.java
│ ├── config
│ ├── controller
│ ├── dao
│ ├── model
│ └── service

└── resources
├── application.properties

Let's Begin!

Spring Security Configurations

Spring Security does 2 important things for us

  1. Authentication: The Check to see if the given user has the right username and password.
  2. Authorization: The Check to see if the user has enough permissions to access the API that they tried to hit.

All that needs to be configured to match what we set out to do. To do this, we need to extend the WebSecurityConfigurerAdapter.

WebSecurityConfigurerAdapter

Start off by annotating the class as @Configuration.

The @EnableWebSecurity is a marker annotation. What that means is that it allows Spring to find and automatically apply this class to the security of the application.

We need to secure our API’s by restricting which roles are able to execute a particular method. This is achieved by adding the annotation @EnableGlobalMethodSecurity(prePostEnabled = true}.

First, take a look at the code, then we will go over each of the methods.

Authentication

Now Spring Security uses something called an AuthenticationManager to validate if a given user has the right credentials (based on username and password). In fact, the AuthenticationManager Interface has exactly one method authenticate which is called to verify if the username and password provided by a user are truthy.

But the AuthenticationManager needs to know where the user’s username and password have been stored.

That is why we override the configure method where Spring will pass an AuthenticationManagerBuilder.

The AuthenticationManagerBuilder accepts a custom implementation of the UserDetailsService interface (which we will implement when we are building our services). Also at this stage, if we are using some form of encryption to store our password in the database, the AuthenticationManager needs to know about that as well. It’s actually a very bad idea to store a password as plaintext. Here, we will be using BCrypt to encode our passwords.

The AuthenticationManager we just configured is added to the Spring Application Context and is added as a bean by overriding the authecationManagerBean method.

Authorization

To set up Authorization, we again need to provide the configuration by overriding the configure method, where we are passed a reference to the default HttpSecurity configuration.

Here we are configuring such that we will require authentication for all requests, with the exception of /users/register & /users/authenticate (We require those two endpoints to be available to all users to sign-up or login).

For the graceful handling of Unauthorized requests, we pass along a class that implements AuthenticationEntryPoint. We will return a 401 Unauthorized when we encounter an exception.

Because we are using JWT to store roles, we need to translate that into something that Spring Security can understand. The JWT Token needs to be parsed to fetch roles that the SpringSecurityContext needs to become aware of before it goes on to check if the API’s permissions will allow it. Hence we pass along the JwtAuthenticationFilter (Which we will come to in a later step).

JWT Token Provider

Let’s first create a component class that will handle all things JWT. We will call the methods within this class to generate the JWT as well as validate the JWT when the user sends it back to us.

The generateToken method builds and signs the JWT Token that we will pass along to the user as soon as they authenticate. Here is where as part of the payload, we will add the username, roles (comma separated), and the issuedAt and expiration timestamps.

validateToken basically checks if the username on the token payload matches the UserDetails. It also checks if the token has expired.

Spring Security Context holds the information of an authenticated user represented as an Authentication object. In order to construct this Authentication object, we need to provide a UsernamePasswordAuthenticationToken which will later be used by our AuthenticationManager (Which we configured previously) to Authenticate our user. To construct, we are passing along the user details as well as a collection of authorities(roles) that we parse from the JWT Token. This is exactly what's happening inside getAuthenticationToken.

JWT Authentication Filter

Okay, back to the JWTAuthenticationFilter which will filter out requests that have JWT as header and translate that to something Spring Security can understand using the methods from the Token Provider we just created. This extends the OncePerRequestFilter meaning it's going to look for the JWT token in every single request and update the SecurityContext.

This concludes our Security Configurations, Lets move on to creating the services.

We are using some constants that we are fetching by key. These are stored in the .properties file

jwt.token.validity=18000
jwt.signing.key=signingkey
jwt.authorities.key=roles
jwt.token.prefix=Bearer
jwt.header.string=Authorization

Service Layer

Model

Let us first define what a User is. For the purposes of this article, I am defining a user to have

  1. Username (Unique)
  2. Password
  3. Name
  4. Business Title
  5. Roles

A single user can have multiple roles. We define Roles to have

  1. Name
  2. Description

So let's create 2 classes under the model subdirectory, User, and Role. As mentioned earlier, we will be storing all this information in a MySQL Database and so User & Role classes can be written as Entity classes. What that really means is that we will be annotating the member variables as appropriate.

There exists a Many-to-Many relationship between User and Roles, meaning that each user can assume multiple roles and each role can be assumed by many users. So we will annotate that accordingly.

@Entity
public class User {

@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;

@Column
private String username;

@Column
@JsonIgnore
private String password;

@Column
private String email;

@Column
private String phone;

@Column
private String name;

@Column
private String businessTitle;

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "USER_ROLES",
joinColumns = {
@JoinColumn(name = "USER_ID")
},
inverseJoinColumns = {
@JoinColumn(name = "ROLE_ID") })
private Set<Role> roles;

}

If you’re wondering what @JsonIgnore does, because we store password along with other user details, we don’t want it returned as a response to a query that returns the user object. This takes care of stripping the password from the API response.

@Entity
public class Role {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;

@Column
private String name;

@Column
private String description;
}

We will also be needing a Data Transfer Object for User entity, let's call it UserDto. I’ve added a new method getUserFromDto to convert it to a User object.

public class UserDto {

private String username;
private String password;
private String email;
private String phone;
private String name;
private String businessTitle;

public User getUserFromDto(){
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
user.setPhone(phone);
user.setName(name);
user.setBusinessTitle(businessTitle);

return user;
}
}

While we are at it, we need to set up the JPA configuration. This can be added to the .properties file.

spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.user.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5

NOTE: I am not walking through the process of setting up your MySQL DB Instance. You can have it run locally on your machine, a docker instance, or on the cloud, wherever you want it. Accordingly, your URL, username, and password values may change.

It should be apparent to you from the configuration that we have created a schema called “springsecurity” which will eventually have 3 tables. user, role, and the 3rd table which is user_role which will map the user to the roles. (The Many-to-Many relationship we established earlier).

DAO

To perform read and writes on our database, we will create the UserDao and RoleDao repositories (annotated with @). These are interfaces that extend CrudRepository<?,?> where the 1st parameter represents the object model, in this case, User and Role. In each case, the 2nd parameter should be datatype of the unique id of each user or role object.

Now, in addition to the methods the CRUD repository brings to the table, we can add additional derived queries.

A derived query method name has two main components. An introducer such asfind, read, query, count, or get. And a criterion that starts after the first By keyword. The first By behaves as the delimiter to indicate the start of the actual query criteria. The amazing thing about derived queries is that the method names are automatically parsed into queries, so we don't have to do much else apart from defining the methods themselves.

For the user, we want to be able to query by username and for the role, we want to be able to lookup by name. So we will introduce a findByUsername method in the UserDao and findRoleByName in the RoleDao.

@Repository
public interface UserDao extends CrudRepository<User, Long> {
User findByUsername(String username);
}
@Repository
public interface RoleDao extends CrudRepository<Role, Long> {
Role findRoleByName(String name);
}

Service

We can now get to the step of writing the services. The services will ultimately use the service methods in our controller, so ultimately, this is where our core business logic might live.

In the interest of best practice, we will be drawing out blueprints in the form of interfaces for our user and role services.

public interface UserService {
User save(UserDto user);
List<User> findAll();
User findOne(String username);
}
public interface RoleService {
Role findByName(String name);
}

We will implement these interfaces into our Services.

Let’s start with the RoleServiceImpl. Pretty Straightforward. Implement the RoleService Interface and Override the findByName method. Now in order to fetch the role, we will access our RoleDao which has the requisite methods to query the DB and find role by name. Simply Autowire the RoleDao into our Service and our job here is pretty much done.

@Service(value = "roleService")
public class RoleServiceImpl implements RoleService {

@Autowired
private RoleDao roleDao;

@Override
public Role findByName(String name) {
Role role = roleDao.findRoleByName(name);
return role;
}
}

All we have left is UserServiceImpl. Now you may remember from before that we need to still implement the UserDetailsService with the lone loadUserByUsername method. We can have our UserServiceImpl also implement the UserDetailsService interface in addition to the UserService Interface.

The most interesting method here is the loadByUsername, which looks up the user through the DAO repository for the user object and returns a new org.springframework.security.core.userdetails.User constructed with username, password, and a Set of granted authorities.

The implementations of findAll(), findOne() and save(); are self explanatory. You will notice that in the save() method, we are assigning every user with “ROLE_USER”. Here we get the chance to add some additional business logic to determine if the user is also an admin. For the purposes of this article, I am assuming that any user with the domain @admin.edu will additionally have admin privileges.

Controller

Finally! The last steps!

The first thing the user needs to do is to register. The bare minimum that we need to provide is a username and password. simply calling the service method to save the user does the trick.

In order to access the APIs, we need to pass along a server-generated JWT Token. We have done all the groundwork for that in our TokenProvider. We use the generateTokenMethod and pass along the response.

The other 2 API’s are just here for testing.

Add the 2 new DTOs Token and LoginUser

public class LoginUser {
private String username;
private String password;
}
public class AuthToken {
private String token;
}

And that's it! We have all the pieces we need!

Finishing Steps

We first need to create the tables in our DB populate them where needed.

Create another file called query.sql file alongside our .properties file and configure the data source to point to our “springsecurity” database.

The first step is to create the tables. Add the following statements. Note, you can only have DDL execute here.

drop table if exists role;
drop table if exists user;
drop table if exists user_roles;
create table role (id bigint not null auto_increment, description varchar(255), name varchar(255), primary key (id)) engine=MyISAM;create table user_roles (user_id bigint not null, role_id bigint not null, primary key (user_id, role_id)) engine=MyISAM;

With that step completed, you can go ahead and start your server!

You will notice that upon startup the 3 new tables are created in your MySql Database.

Time to populate our role Table with all the roles. Run the following statements

INSERT INTO role (id, description, name) VALUES (4, 'Admin role', 'ADMIN');
INSERT INTO role (id, description, name) VALUES (5, 'User role', 'USER');

Testing

For your convenience, I have created a Postman Collection that you will find in the Github Repository under the name SpringSecurity.postman_collection.json

Just run the tests in order one by one. Be sure to add host and port to your environment variables before you run them.

Happy Fun!

And of course, If you’d rather just look at how everything is in code and figure it out yourself, Here’s the Github Repo

https://github.com/akhileshmalini/spring-security-role-jwt.git

--

--