Spring Boot Security Database Authentication Tutorial

In this tutorial, we will walk through the process of setting up database authentication in a Spring Boot application using the latest version of Spring Security. We'll create User and Role entities with a many-to-many relationship, configure Spring Security for database authentication, and insert a few records during application startup.

What is Database Authentication?

Database authentication is a method of validating users against credentials stored in a database. This is more flexible and scalable compared to in-memory authentication, as it allows dynamic management of users and roles. User details and roles are stored in database tables, and Spring Security uses these tables to authenticate users and authorize their access to various parts of the application.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • An IDE (Integrated Development Environment) like IntelliJ IDEA or Eclipse

Step 1: Setting Up the Project

Create a Spring Boot Project

  1. Open your IDE and create a new Spring Boot project using Spring Initializr.
  2. Add the following dependencies:
    • Spring Web
    • Spring Security
    • Spring Data JPA
    • H2 Database (for simplicity, but you can use any database of your choice)
    • Spring Boot Starter Thymeleaf (for creating simple views)

Project Structure

Your project structure should look like this:

Spring Boot Security Database Authentication Tutorial

Step 2: Adding Dependencies

Add the necessary dependencies for Spring Security, Spring Data JPA, H2 database, and Thymeleaf in the pom.xml file.

pom.xml

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Starter Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- Spring Boot Starter Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- H2 Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- Spring Boot Starter Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Step 3: Configuring the Application Properties

Configure the application properties for the H2 database and other settings.

application.properties

# H2 Database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# JPA settings
spring.jpa.hibernate.ddl-auto=update

Step 4: Creating the User and Role Entities

Create User and Role entities with a many-to-many relationship in the com.example.security.model package.

Role.java

package com.example.security.model;

import jakarta.persistence.*;
import java.util.Set;

@Entity
public class Role {

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

    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;

    // Getters and Setters

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

User.java

package com.example.security.model;

import jakarta.persistence.*;
import java.util.Set;

@Entity
public class User {

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

    private String username;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles;

    // Getters and Setters

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Step 5: Creating the Repositories

Create UserRepository and RoleRepository interfaces in the com.example.security.repository package.

UserRepository.java

package com.example.security.repository;

import com.example.security.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

RoleRepository.java

package com.example.security.repository;

import com.example.security.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleRepository extends JpaRepository<Role, Long> {
    Role findByName(String name);
}

Step 6: Creating the User Service

Create a UserDetailsServiceImpl class to load user-specific data and a UserService class to manage users.

UserDetailsServiceImpl.java

package com.example.security.service;

import com.example.security.model.User;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .roles(user.getRoles().stream().map(role -> role.getName()).toArray(String[]::new))
                .build();
    }
}

UserService.java

package com.example.security.service;

import com.example.security.model

.Role;
import com.example.security.model.User;
import com.example.security.repository.RoleRepository;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.HashSet;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostConstruct
    public void init() {
        // Create roles
        Role adminRole = new Role();
        adminRole.setName("ADMIN");
        roleRepository.save(adminRole);

        Role userRole = new Role();
        userRole.setName("USER");
        roleRepository.save(userRole);

        // Create users
        User admin = new User();
        admin.setUsername("admin");
        admin.setPassword(passwordEncoder.encode("admin"));
        admin.setRoles(new HashSet<>(Set.of(adminRole)));
        userRepository.save(admin);

        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder.encode("password"));
        user.setRoles(new HashSet<>(Set.of(userRole)));
        userRepository.save(user);
    }
}

Step 7: Configuring Spring Security

Create a SecurityConfig class in the com.example.security package to configure Spring Security for database authentication.

SecurityConfig.java

package com.example.security;

import com.example.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/h2-console/**").permitAll()  // Allow access to H2 console
                    .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());

        // Disable CSRF and frame options for H2 console
        http.csrf().disable();
        http.headers().frameOptions().disable();

        return http.build();
    }

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

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

Step 8: Creating the Hello Controller

Create a HelloController class in the com.example.security.controller package to expose a secured endpoint.

HelloController.java

package com.example.security.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}

Step 9: Creating the Main Application Class

Create the main application class to run your Spring Boot application.

SecurityApplication.java

package com.example.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
}

Step 10: Running the Application

To run the application, execute the SecurityApplication class. This will start the Spring Boot application with Spring Security configured for database authentication.

Accessing the Application

  1. Open your browser and navigate to http://localhost:8080/h2-console to access the H2 database console. Use the JDBC URL jdbc:h2:mem:testdb, username sa, and an empty password to log in.
  2. Navigate to http://localhost:8080/api/hello. You will be prompted to enter a username and password. Use the credentials defined in your UserService:
    • username: admin
    • password: admin
    • username: user
    • password: password

Using curl to Access the API

You can also use curl to access the secured API:

curl -u user:password http://localhost:8080/api/hello

This command sends a request to the API with HTTP Basic authentication using the user credentials.

Conclusion

In this tutorial, we have walked through setting up a basic Spring Boot application and integrating it with Spring Security for database authentication. We created User and Role entities with a many-to-many relationship, configured Spring Security to authenticate users from the database, and inserted initial records during application startup. By following this tutorial, you should now have a good understanding of how to integrate Spring Security with Spring Boot and secure your applications using database authentication.


Comments