Database-Based Authentication in Spring Security: A Step-by-Step Tutorial

In this tutorial, we will explore how to implement database-based authentication using Spring Security. We’ll create a simple web application with user registration, login, and access control.

Table of Content:

Setting Up the Project

Let’s start by setting up a new Spring Boot project. You can do this manually or use Spring Boot Initializr to generate the project structure. Ensure you include the following dependencies:

  • Spring Boot DevTools
  • Spring Web
  • Spring Data JPA
  • H2 Database (for simplicity, we’ll use an in-memory database)

Our pom.xml file will look like below

<?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.7.14</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example.securitytutorial</groupId>
	<artifactId>database-authentication-spring-security</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>database-authentication-spring-security</name>
	<description>Database-Based Authentication in Spring Security</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

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

</project>

Creating the User Entity

First, let’s create a simple User entity to represent our application’s users. Create a new package com.example.securitytutorial.model and add the following class:

package com.example.securitytutorial.model;

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

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    // Add other user-related fields as needed
    // Constructors, getters, and setters
}

Implementing User Repository

Next, we’ll create a repository to interact with the User entity. Create a new package com.example.securitytutorial.repository and add the following interface:

package com.example.securitytutorial.repository;

import com.example.securitytutorial.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

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

Configuring Spring Security

Now, it’s time to configure Spring Security to use our database for authentication. Create a new package com.example.securitytutorial.config and add the following class:

package com.example.securitytutorial.config;

import com.example.securitytutorial.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/register").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/dashboard")
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login")
            .permitAll();
    }

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

Creating CustomUserDetailsService

We will implement a custom UserDetailsService that will fetch user details from the database. Create a new package com.example.securitytutorial.service and add the following class:

package com.example.securitytutorial.service;

import com.example.securitytutorial.model.User;
import com.example.securitytutorial.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 CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));

        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .roles("USER") // Add roles as needed for access control
            .build();
    }
}

Implementing User Registration and Login Controllers

Create a new package com.example.securitytutorial.controller and add the following classes to handle user registration and login:

RegistrationController.java

package com.example.securitytutorial.controller;

import com.example.securitytutorial.model.User;
import com.example.securitytutorial.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class RegistrationController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @GetMapping("/register")
    public String showRegistrationForm() {
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(@RequestParam String username, @RequestParam String password) {
        User user = new User();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(password)); // Encrypt the password before saving
        // Set other user details if needed

        userRepository.save(user);
        return "redirect:/login";
    }
}

LoginController.java

package com.example.securitytutorial.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String showLoginForm() {
        return "login";
    }
}

Creating Web Pages For Spring Security

Finally, create the following HTML files under the src/main/resources/templates folder:

register.html

<!DOCTYPE html>
<html>
<head>
    <title>User Registration</title>
</head>
<body>
    <h1>User Registration</h1>
    <form method="post" action="/register">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br>
        <input type="submit" value="Register">
    </form>
</body>
</html>

login.html

<!DOCTYPE html>
<html>
<head>
    <title>User Login</title>
</head>
<body>
    <h1>User Login</h1>
    <form method="post" action="/login">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br>
        <input type="submit" value="Login">
    </form>
</body>
</html>

Testing the Spring Security Application

Run the Spring Boot application, and you should be able to access the following endpoints:

  • /register: User registration page
  • /login: User login page
  • /logout: Logout endpoint (POST request to log out)

When a user registers, their information will be saved in the database with an encrypted password. When a user logs in, Spring Security will authenticate the user against the database, and if the credentials match, the user will be redirected to /dashboard.

The End!!!

In this tutorial, we explored how to implement database-based authentication in a Spring Boot application using Spring Security. We created a simple user entity, implemented user registration and login functionality, and used the H2 database for demonstration purposes. In real-world scenarios, you would typically use a production-ready database like MySQL or PostgreSQL.

Remember that this is just a basic implementation, and in a production environment, you would need to consider additional security aspects like password policies, account locking, and other security best practices. Happy coding!

Leave a Reply

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

Post comment