REST API Authentication with Spring Security and MongoDB

Hello! Leaving for the weekend we share with you an article that was translated in anticipation of the start of the course “Developer on the Spring Framework”.


In previous articles, we created a RESTful web service, now we’ll talk about security

Introduction

In a previous post, we looked at how to create a REST API using the Java Spring Boot and MongoDB frameworks. The API, however, did not require any authentication, which means that it is probably still not ready for use. Therefore, this tutorial will explain how to use the Spring’s built-in security environment to add an authentication level to this API.

Why does our API need authentication?

The APIs provide a simple interface for interacting with internal data, so it makes sense that you do not want anyone to have access to this data and change it. Authentication ensures that only trustworthy users can access the API.

How it works

We will use basic HTTP Authentication that uses a username and password. The username and password are separated on one line by a colon in the following format username:password.

This string is then encoded using encoding Base64, so the line admin:p@55w0Rd will be encoded to the next line YWRtaW46cEA1NXcwUmQ= (although I would suggest using a stronger password than “p @ 55w0Rd”). We can attach this authentication to our requests by adding a header. Authentication. This heading for the previous example would look like this (where “Basic” means the password uses basic HTTP authentication):

Authentication: Basic YWRtaW46cEA1NXcwUmQ=

How Spring Manages Security

Spring offers an add-in called Spring securitywhich makes authentication highly customizable and extremely simple. We can even use some of the skills that we learned in a previous post when setting up!

What we need

  • A new collection in our MongoDB instance called “users”
  • A new document in the users collection with the following fields (any other fields are optional, but these are necessary): username, password (hashed using the BCrypt algorithm, more on that later)
  • Sources from the previous post

BCrypt for password hashing

Hashing is a one-way encryption algorithm. In fact, after hashing, it’s almost impossible to discover what the original data looked like. The BCrypt hash algorithm first salts a piece of text and then hashes it to a string of 60 characters. Java BCrypt Encoder Offers Method matcheswhich checks if the string matches the hash. For example, password p@55w0Rdhashed with BCrypt may matter $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS. When calling a method matches BCrypt for unencrypted and hashed password we get the value true. These hashes can be generated using the BCrypt encoder built into Spring Security.

Why should we hash passwords?

We have all heard about recent cyber attacks that resulted in stolen passwords from large companies. So why is it only recommended to change our passwords after hacking? Because these large companies have made sure that passwords are always hashed in their databases!

Although it’s always worth it to change passwords after such data hacks, hashing passwords makes it extremely difficult to find the user’s real password, since it is a one-way algorithm. In fact, you may need yearsto crack a complex password hashed properly. This provides an additional layer of protection against password theft. And Spring Security simplifies hashing, so the real question should be: “Why not?”

Adding a user to MongoDB

I will add a minimum of fields required for my collection users (users), so the document with users in my database will only contain username (username) and hashed BCrypt password (password). In this example, my username will be admin, and my password will be welcome1, but I would suggest using a more robust username and password in the production level API.

db.users.insert({
  “username” : “admin”,
  “password” : “$2a$10$AjHGc4x3Nez/p4ZpvFDWeO6FGxee/cVqj5KHHnHfuLnIOzC5ag4fm”
});

These are all the settings needed in MongoDB! The rest of the configuration will be done in our Java code.

Adding a user model and repository

The previous post described in detail the Mongo models and repositories, so I will not go into details about how they work here – if you want to refresh your knowledge, do not hesitate to visit my previous post!

The downside is that Spring needs to know what the document will look like. user (model) and how to access the collection user in the database (repositories). We can put these files in the same folders of models and repositories, respectively, as we did in the previous exercise.

Model

The model will be a Java base class with custom _id, username and password. File will be called Users.java. and will look like this:

package com.example.gtommee.rest_tutorial.models;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;

public class Users {
  @Id
  public ObjectId _id;

  public String username;
  public String password;

  public Users() {}

  public Users(ObjectId _id, String username, String password) 
{
    this._id = _id;
    this.username = username;
    this.password = password;
  }

  public void set_id(ObjectId _id) { this._id = _id; }

  public String get_id() { return this._id.toHexString(); }

  public void setPassword(String password) { this.password = password; }

  public String getPassword() { return password; }

  public void setUsername(String username) { this.username = username; }

  public String getUsername() { return username; }
}

Repository

The repository will be called UsersRepository.java and it will look like this – remember, we will need to find users by their username, so we will need to enable the method findByUsername to the repository interface.

package com.example.gtommee.rest_tutorial.repositories;

import com.example.gtommee.rest_tutorial.models.Users;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface UsersRepository extends MongoRepository {
  Users findByUsername(String username);
}

And that’s it for the model and the repository!

Adding Security Dependencies

The root directory of the project should have a file named pom.xml. We have not touched this file yet, but the pom file contains all the dependencies of our project, and we are going to add a couple to it, so let’s start by opening this file and scrolling down to the tag <dependencies>.

The only new addiction we need is spring-starter-security. Spring has a built-in version manager, so the dependency we have to add to the tag <dependencies>following:


  org.springframework.boot
  spring-boot-starter-security

And Maven will download the source files for us, so our dependencies must be ready to go!

Create Authentication Service

We need to tell Spring where our user data is and where to find the information needed for authentication. To do this, we can create an Authentication Service. Let's start by creating a new folder in src/main/resources/java/[package name] called services, and we can create a new file in this configuration folder with the name MongoUserDetailsService.java.

MongoUserDetailsService.java

This class has one main component, so I’ll just give the whole class here and then explain it below:

package com.example.gtommee.rest_tutorial.services;

import com.example.gtommee.rest_tutorial.models.Users;
import com.example.gtommee.rest_tutorial.repositories.UsersRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class MongoUserDetailsService implements UserDetailsService{
  @Autowired
  private UsersRepository repository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Users user = repository.findByUsername(username);

    if(user == null) {
      throw new UsernameNotFoundException(“User not found”);
    }

    List authorities = Arrays.asList(new SimpleGrantedAuthority(“user”));

    return new User(user.getUsername(), user.getPassword(), authorities);
  }
}

This snippet begins with the imports we need in the file. Next section implements UserDetailsService indicates that this class will create a service for searching and authenticating users. Then, annotation @Component indicates that this class can be embedded in another file (for example, the SecurityConfiguration file, which we will go through several sections).

annotation @Autowired above private UsersRepository repository; is an implementation example, this property provides us with an instance of our UsersRepository for work. annotation @Override indicates that this method will be used instead of the default UserDetailsService method. This method first gets the object Users from MongoDB data source using method findByUsernamewhich we announced in UsersRepository.

The method then checks whether the user was found or not. Then the user is granted permissions / role (this can add additional authentication levels for access levels, but one role will be enough for this lesson). Finally, the method returns a Spring object User from username, password and role authenticated user.

Create Security Configuration

We will need to redefine some of Spring’s built-in security protocols to use our database and hash algorithm, so we need a special configuration file. To create it, we need to create a new folder in src/main/resources/java/[package name] With name config, and we also need to create a new file in this configuration folder with the name SecurityConfiguration.java. This file has several important parts, so let's start with the SecurityConfiguration base class:

SecurityConfiguration.java

package com.example.gtommee.rest_tutorial.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;
}

There is already enough to deal with, so let's start from above. annotation @Configuration indicates that the class will contain Java beans, described in detail here. annotation @EnableConfigurationProperties indicates that the class will contain as a special configuration bean. Then instruction extends WebSecurityConfigurerAdapter map our parent class to the configuration class WebSecurityConfigurerAdapterby providing our class with everything necessary to ensure compliance with its safety rules. Finally, the class will automatically inject the instance (@Autowired) MongoUserDetailsServicewhich we can use later in this file.

Authentication step

Next, we need to tell Spring Security how we want to handle user authentication. By default, Spring Security has a predefined username and password, CSRF Protection and session management. However, we want our users to use their username and password to access the database. In addition, since our users will be re-authenticated at each request, rather than logging in, we do not need CSRF Protection and session management, so we can add a method called configure, which overrides the default authentication scheme to tell Spring exactly how we want to handle authentication, and will look like this:

@Override
protected void configure(HttpSecurity http) throws Exception {
 http
   .csrf().disable()
   .authorizeRequests().anyRequest().authenticated()
   .and().httpBasic()
   .and().sessionManagement().disable();
}

Again, quite a lot of things are happening here, so we will sort it out in stages. annotation @Override tells Spring Boot to use the method configure (HttpSecurity http) instead of the default Spring configuration. Then we call a series of methods for the object httpwhere the actual configuration takes place. These methods do the following:

  • csrf().disable(): Disables CSRF Protection because it is not needed for the API
  • authorizeRequests().anyRequest().authenticated(): Declares that all requests to any endpoint must be authorized, otherwise they must be rejected.
  • and().httpBasic(): сообщает Springso that it expects basic HTTP authentication (discussed above).
  • .and().sessionManagement().disable(): tells Spring not to store session information for users, as this is not necessary for the API

Adding a Bcrypt Encoder

Now we need to tell Spring to use the BCrypt encoder for hashing and comparing passwords - this sounds like a difficult task, but in fact it is very simple. We can add this encoder by simply adding the following lines to our class SecurityConfiguration:

@Bean
public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
}

And all the work! This simple bean tells Spring that the PasswordEncoder we want to use is Spring Boot BCryptPasswordEncoder() for encoding and comparing password hashes. Spring Boot also includes several other password encoders - I recommend trying them if you want to experiment!

Specify Authentication Manager

Finally, we must indicate in our SecurityConfigurationwhat we want to use MongoUserDetailsService (which we created in the previous section) for our authentication. We can do this using the following method:

@Override
public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
  builder.userDetailsService(userDetailsService);
}

This method simply overrides the default configuration. AuthenticationManagerBuilderreplacing our own automatic user data transfer service instead.

Final SecurityConfiguration.java file

import com.example.gtommee.rest_tutorial.services.MongoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 MongoUserDetailsService userDetailsService;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http
     .csrf().disable()
     .authorizeRequests().anyRequest().authenticated()
     .and().httpBasic()
     .and().sessionManagement().disable();
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
   return new BCryptPasswordEncoder();
 }

 @Override
 public void configure(AuthenticationManagerBuilder builder) 
throws Exception {
   builder.userDetailsService(userDetailsService);
 }
}

Authentication check

I will test fast GET- A request with valid and incorrect authentication to ensure that the configuration works as planned.

Incorrect Username / Password

URL: http://localhost:8080/pets/

Method: GET

Authorization: Basic YWRtaW46d2VsY29tZQ==

Answer:

401 Unauthorized

Valid Username / Password

URL: http://localhost:8080/pets/

Method: GET

Authorization: Basic YWRtaW46d2VsY29tZTE=

Answer:

[
 {
   “_id”: “5aeccb0a18365ba07414356c”,
   “name”: “Spot”,
   “species”: “dog”,
   “breed”: “pitbull”
 },
 {
   “_id”: “5aeccb0a18365ba07414356d”,
   “name”: “Daisy”,
   “species”: “cat”,
   “breed”: “calico”
 },
 {
   “_id”: “5aeccb0a18365ba07414356e”,
   “name”: “Bella”,
   “species”: “dog”,
   “breed”: “australian shepard”
 }
]

Conclusion

It works as it should! Basic HTTP authentication for the Spring Boot API can be complicated, but hopefully this guide will help make it more understandable. Authentication is a must in today's cyber climate, so tools like Spring Security are critical to ensure the integrity and security of your data.

That's all! Meet me at course.

Similar Posts

One Comment

  1. In a spring project with multiple controllers can this be set up for a single specific controller while leaving the others open?

Leave a Reply

Your email address will not be published. Required fields are marked *