Spring Boot Security with Database Authentication

In this article we are going to see how can we perform authentication using database and spring security.

Before we go for an example, it is important to understand how Spring Security works.

Working of Spring Security Internally:

Spring Security Internal Working

Steps:

  • User will enter his credentials
  • Authentication Filter: The request will be intercepted by Authentication filter. After intercepting it will convert the credentials to Authentication Object.
  • Authentication Object: Contains the user credentials for validation
  • Authentication Manager: Authentication Manager will identify corresponding Authentication Provider and will forward the request to appropriate Authentication Provider. An Application can have any number of Authentication providers, it is the responsibility of Authentication Manager to verify with all Authentication Provider until the match is found.
  • Authentication Provider: Which contains the logic for validating the credentials

There are 2 interfaces within that,

User Details Service and Password Encoder.

User Details Service holds the user schema

Password Encoder helps us with password encryption.

In this article we are going to use custom authentication provider to validate our credentials from database.

Project Structure:

pom.xml


<?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.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.javainfinite</groupId>
    <artifactId>security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security</name>
    <description>Spring Security Basic</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Model Student class (Entity class)

Student.java


package com.javainfinite.security.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Student {

    @Id
    @GeneratedValue
    private Integer id;

    @Column(name = "username")
    private String sname;

    @Column(name = "password")
    private String password;

    @Column(name = "roles")
    private String srole;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public String getPassword() {
        return password;
    }

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

    public String getSrole() {
        return srole;
    }

    public void setSrole(String srole) {
        this.srole = srole;
    }
}

JpaRepository for Students (CRUD in database)

StudentRepository.java


package com.javainfinite.security.repository;

import com.javainfinite.security.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

    public Student findBySname(String username);
}

We are going to have a service method for providing actual logic for our methods in interface.

StudentService.java


package com.javainfinite.security.service;

import com.javainfinite.security.model.Student;
import com.javainfinite.security.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    public Student register(Student student) {
        return studentRepository.save(student);
    }

    public Student getDetails(String username) {
        return studentRepository.findBySname(username);
    }

    public String getStudentRoles(String username) {
        return studentRepository.findBySname(username).getSrole();
    }
}

We are going to write 3 API’s,

  • /register – for user registration
  • /studentInfo – Details regarding the student
  • /getStudentRoles – Roles of the student

StudentController.java


package com.javainfinite.security.controller;

import com.javainfinite.security.model.Student;
import com.javainfinite.security.service.StudentService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;


@RestController
public class StudentController {

    private StudentService service;

    private PasswordEncoder encoder;

    public StudentController(StudentService service, PasswordEncoder encoder) {
        this.service = service;
        this.encoder = encoder;
    }

    /**
     * Any user can access this API - No Authentication required
     * @param student
     * @return
     */

    @PostMapping("/register")
    public Student registerStudent(@RequestBody Student student) {
        Student student1 = new Student();
        student1.setSname(student.getSname());
        student1.setPassword(encoder.encode(student.getPassword()));
        student1.setSrole(student.getSrole());
        return service.register(student1);
    }

    /**
     * User who has logged in successfully can access this API
     * @param username
     * @return
     */
    @GetMapping("/studentInfo")
    public Student getStudentInfo(@RequestParam("sname") String username) {
        return service.getDetails(username);
    }

    /**
     * User who has the role ROLE_WRITE can only access this API
     * @param username
     * @return
     */
    @GetMapping("/getStudentRoles")
    public String getStudentRoles(@RequestParam("sname") String username) {
        return service.getStudentRoles(username);
    }
}

Now let us write the code for security,

For setting the access for specific URI’s we need to create a class that extends WebSecurityConfigurerAdapter

StudentSecurityConfig.java

package com.javainfinite.security.security;

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
public class StudentSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/studentInfo").authenticated()
                .antMatchers("/register").permitAll()
                .antMatchers("/getStudentRoles").hasAuthority("ROLE_WRITE")
                .and()
                .httpBasic();
    }

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

We have created a class StudentSecurityConfig which extends webSecurityConfigurerAdapter and we are overriding the method

protected void configure(HttpSecurity http) throws Exception

First thing is we need to disable csrf. so what is CSRF?

CSRF stands for Cross Site Request Forgery. Attacker tries to perform some action on behalf of user without his consent. For example, Attacker might prompt the user to fill some form and will perform some action on behalf of user once user submits the form.

Next we are authorizing requests and configuring which API’s require restriction and which can be accessed by all users.

In our example, we have configured in such a way that,

“/studentInfo” – can be access by any user who has successfully ayuthenticated.

“/register” – Anyone can access this API.

“/getStudentRoles” – can be accessed by the user who has role/authority – “ROLE_WRITE”

We have declared a piece of code as bean,

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

Whenever we use Spring Security it is mandatory for use Password Encoder, There are many password encoders like – NoOpPasswordEncoder, StandardPasswordEncoder, BCryptPasswordEncoder etc.

In our example we are going to use BCryptPasswordEncoder to encode the password and save it in database.

Now let us implement our own Authentication Provider. From the above flow we can see that once the user enters the credentials, The flow will go to Authentication Filter which intercepts the request and creates an Authentication Object and sends the request to Authentication Manager.

Authentication manager then will check with all available Authentication providers to find the matching one and validate the credentials for us.

StudentAuthenticationProvider.java


package com.javainfinite.security.security;

import com.javainfinite.security.model.Student;
import com.javainfinite.security.repository.StudentRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class StudentAuthenticationProvider implements AuthenticationProvider {

    Logger logger = LoggerFactory.getLogger(StudentAuthenticationProvider.class);

    private StudentRepository repository;

    private PasswordEncoder encoder;

    public StudentAuthenticationProvider(StudentRepository repository, PasswordEncoder encoder) {
        this.encoder = encoder;
        this.repository = repository;
    }

    /**
     * Get the username and password from authentication object and validate with password encoders matching method
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        Student student = repository.findBySname(username);
        if (student == null) {
            throw new BadCredentialsException("Details not found");
        }

        if (encoder.matches(password, student.getPassword())) {
            logger.info("Successfully Authenticated the user");
            return new UsernamePasswordAuthenticationToken(username, password, getStudentRoles(student.getSrole()));
        } else {
            throw new BadCredentialsException("Password mismatch");
        }
    }

    /**
     * An user can have more than one roles separated by ",". We are splitting each role separately
     * @param studentRoles
     * @return
     */
    private List<GrantedAuthority> getStudentRoles(String studentRoles) {
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        String[] roles = studentRoles.split(",");
        for (String role : roles) {
            logger.info("Role: " + role);
            grantedAuthorityList.add(new SimpleGrantedAuthority(role.replaceAll("\\s+", "")));
        }

        return grantedAuthorityList;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

After starting the application when basic authentication (username and password) are provided, the Authentication Filter will intercept the request and forwards it to Authentication Manager.

Authentication Manager will then find this StudentAuthenticationProvider class and will execute the authenticate method.

Here we are getting the username and password from user and fetching the details from database and using Password Encoder matching method, we are comparing both the passwords.

If the passwords matched, User is authenticated or we will throw BadCredentialsException.

Now let us start the application and will check the functionality.

Registering the users,

User 1:

User 2:

We have created 2 users, user Alpha who has roles – ROLE_READ and ROLE_WRITE and user beta who has only role – ROLE_READ

Here we are using basic authentication by entering username and password and trying to get the details for the Student – Alpha.

When we try the same thing without providing authentication, we will get unauthorized error,

Cause,

We have configured in such a way that the user has to be authenticated for accessing this API.

Now let us check based on Authority, “/getStudentRoles” as per our example – Alpha can access it as he has ROLE_WRITE and beta should not be able to access it as he has only ROLE_READ.

Now let us try this,

When we try to access this API as beta user, we get the error forbidden. The user is authenticated but he is not authorized to access this API.

Now let us try the same with Alpha user,

Now with user alpha we are trying to get the roles for user “beta” which is ROLE_READ.

We have successfully learnt how to authenticate and restrict api’s based on roles in this article.

Lets meet soon in next article.

Code can be downloaded here.

By Sri

One thought on “Spring Boot Security with Database Authentication”

Leave a Reply

Your email address will not be published.