Spring Security in Spring Boot — A Complete Beginner's Guide

I am Computer Science Graduate and Web Developer.
Introduction
In this guide, we’ll walk through how to set up Spring Security in a Spring Boot application using a custom user model, connect it to a PostgreSQL database, create a signup endpoint, encrypt passwords with BCrypt, and authenticate users securely.
We’ll cover:
Required dependencies
PostgreSQL configuration in
application.propertiesCreating user model and repository
Writing controller for signup
Creating a custom user detail service
Configuring Spring Security
Lifecycle of authentication in Spring Security
Required Dependencies in pom.xml
Before we start writing code, let's first discuss the dependencies that need to be included in the project. These dependencies will provide us with essential tools to handle database interactions, web services, and Spring Security.
Spring Boot Starter Data JPA
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Purpose: This dependency is required to work with databases using JPA (Java Persistence API). It enables Spring Data JPA support and helps us interact with the PostgreSQL database seamlessly.
Benefit: With Spring Data JPA, we don’t have to write SQL queries manually. It provides powerful query capabilities and automatic entity mapping.
Spring Boot Starter Web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Purpose: This dependency adds everything needed to build web applications, including RESTful web services. It integrates Spring MVC, Tomcat as the default container, and Jackson for JSON binding.
Benefit: It allows us to create REST APIs and interact with users using HTTP requests (like GET, POST).
Spring Boot DevTools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
Purpose: DevTools enhances the development experience by providing features like auto-restart and live reload. It automatically restarts the application when you make changes to the code, which saves time.
Benefit: It allows faster development cycles by reloading your application when you modify code, making it easier to see changes immediately.
Spring Boot Starter Security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Purpose: This dependency brings Spring Security into the project. Spring Security is a powerful and customizable authentication and access-control framework.
Benefit: With Spring Security, we can easily configure authentication, authorization, and protect APIs from unauthorized access.
PostgreSQL Driver
xmlCopyEdit<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
Purpose: This dependency is required to connect Spring Boot to PostgreSQL. It provides the necessary JDBC driver to interact with PostgreSQL databases.
Benefit: It allows the Spring Boot application to connect to a PostgreSQL database to store and retrieve data.
Lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
Purpose: Lombok helps reduce boilerplate code in Java classes. It generates common methods like getters, setters, constructors,
toString(), etc., at compile-time.Benefit: It makes your code cleaner, with less code to write and maintain.
Configuring application.properties
In Spring Boot applications, the application.properties (or application.yml) file is where you configure various properties that control the behavior of the application, such as database connections, server port, logging, and more.
Spring Application Name & Server Port
spring.application.name=demo
server.port=8080
spring.application.name=demo: This sets the name of your Spring Boot application to "demo". It's helpful for logging and monitoring purposes.server.port=8080: This specifies the port on which your application will run. The default is8080, but you can change it to any port number you prefer.
Database Configuration (PostgreSQL)
spring.datasource.url=jdbc:postgresql://localhost:5432/customer
spring.datasource.username=postgres
spring.datasource.password=12345
spring.datasource.url: This is the JDBC URL for connecting to the PostgreSQL database. Here,localhostrepresents the database host,5432is the default PostgreSQL port, andcustomeris the database name.spring.datasource.username: The username to connect to the PostgreSQL database. In this case, it’s postgres.spring.datasource.password: The password for thepostgresuser in the database. Change this to match your database credentials.
Hibernate JPA Configuration
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update: This setting automatically updates the database schema based on the entities in your application. The options are:none: No schema management.update: Updates the schema to match the entities.create: Drops and creates the schema every time the application starts.validate: Validates the schema but does not modify it.In production, avoid using
createorupdate, as it may cause data loss.
spring.datasource.driver-class-name: Specifies the JDBC driver for PostgreSQL.spring.jpa.database-platform: This defines the Hibernate dialect for PostgreSQL. It helps Hibernate generate correct SQL queries for PostgreSQL.
Logging Configuration
propertiesCopyEditlogging.file.name=application.log
logging.level.org.springframework.security=DEBUG
logging.file.name=application.log: This sets the file name for the log output. In this case, the logs will be saved to application.log.logging.level.org.springframework.security=DEBUG: This sets the logging level for Spring Security to DEBUG. It helps in logging detailed information about security-related actions, useful for debugging.
Controller Setup
In Spring Boot, controllers are responsible for handling HTTP requests, processing them, and returning responses. In this section, we’ll walk through the code for a simple UserController class that handles user registration and displays the user list.
UserController Code
Here’s the UserController class:
package com.example.demo.controllers;
import com.example.demo.models.UserModel;
import com.example.demo.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
public class UserController {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserRepository userRepository;
// Endpoint to fetch all users
@GetMapping("/users")
public String GetAllUsers() {
return "Hello World";
}
// Endpoint for user registration
@GetMapping("/signup")
public String Signup(@RequestParam String UserName, @RequestParam String Password)
{
var user = UserModel.builder()
.UserID(UUID.randomUUID().toString()) // Generate a unique ID
.userName(UserName)
.Password(passwordEncoder.encode(Password)) // Encode the password using BCrypt
.build();
userRepository.save(user); // Save the user to the database
return "Registered Successfully"; // Return a success message
}
}
Explanation of Code
@RestController: This annotation marks the class as a REST controller. It means that each method in this class will return data directly to the HTTP response (i.e., no need to render views like with traditional controllers).@Autowired: This annotation is used to automatically inject the dependencies into the class. In this case, we inject:PasswordEncoder: To encrypt the user's password before saving it to the database.UserRepository: This handles interactions with the database to save and retrieve user data.
Repository and Model Setup
In this section, we will set up two crucial components in our Spring Boot application: UserModel and UserRepository.
UserModel Code
The UserModel class represents the structure of the User entity, which is mapped to a table in the database. Here's the code:
package com.example.demo.models;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.*;
@Entity
@Table(name = "Users")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserModel {
@Id
private String UserID; // Primary Key for the User
private String userName; // Username for the user
private String Password; // Password for the user (encoded)
}
Explanation of UserModel Class
@Entity: This annotation marks the class as a JPA entity. JPA (Java Persistence API) is used to interact with the database. TheUserModelclass will be mapped to a table in the PostgreSQL database.@Table(name = "Users"): This annotation specifies the name of the table in the database to which this entity will be mapped. In this case, the table is namedUsers.@Id: This annotation marks theUserIDfield as the primary key for the entity. Each user in theUserstable will have a uniqueUserID.Lombok Annotations:
@AllArgsConstructor: Generates a constructor that accepts all fields as arguments.@NoArgsConstructor: Generates a no-argument constructor.@Getter&@Setter: Automatically generates getter and setter methods for each field.@Builder: This generates a builder pattern for theUserModelclass, which allows us to easily create instances ofUserModelwith method chaining.
UserRepository Code
The UserRepository interface is responsible for interacting with the database. It provides CRUD operations for UserModel objects. Here’s the code:
package com.example.demo.repositories;
import com.example.demo.models.UserModel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<UserModel, String> {
public UserModel findByUserName(String UserName);
}
Explanation of UserRepository Interface
@Repository: This annotation marks the interface as a Spring Data repository. It is used to perform CRUD operations on theUserModelentities in the database.JpaRepository<UserModel, String>: TheJpaRepositoryinterface provides several methods for working with theUserModelentity, such as saving, finding, deleting, and updating records in the database. The generic parameters specify:UserModel: The type of the entity.String: The type of the primary key for theUserModelentity (UserID).
findByUserName(String UserName): This is a custom query method. Spring Data JPA will automatically generate the query to find aUserModelby theUserNamefield. It returns the user associated with the provided username.
Database Mapping in PostgreSQL
When Spring Boot runs with this configuration, it will map the UserModel class to a Users table in the PostgreSQL database. This table will have columns for UserID, userName, and Password. The database will store each user's information, including their encrypted password.
Custom User Details Service
In this section, we will implement a CustomUserDetailService that integrates Spring Security with our UserModel and ensures proper authentication and authorization.
CustomUserDetailService Code
The CustomUserDetailService class implements UserDetailsService and provides a custom way to load a user's details for Spring Security authentication. Here’s the code:
package com.example.demo.services;
import com.example.demo.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findByUserName(username); // Fetch user from the database
if (user == null) { // If no user is found, throw UsernameNotFoundException
throw new UsernameNotFoundException("User not found: " + username);
}
// Returning a UserDetails object with the user info
return User.builder()
.username(user.getUserName()) // Set username
.password(user.getPassword()) // Set password (already encoded)
.roles("USER") // Set the role for this user
.build();
}
}
Explanation of CustomUserDetailService Class
@Service: This annotation marks the class as a service that will be managed by Spring’s dependency injection container. It allows Spring to inject this service into other components like controllers and security configurations.UserDetailsServiceInterface: This is a Spring Security interface that contains a methodloadUserByUsernamewhich is used to fetch user details from a database based on the username provided. It is a core interface used by Spring Security for authentication.loadUserByUsernameMethod:This method takes a
usernameas input and returns aUserDetailsobject. It is responsible for fetching the user information from the database.The
userRepository.findByUserName(username)fetches theUserModelfrom the database using theUserNamefield.If no user is found, a
UsernameNotFoundExceptionis thrown.The
User.builder()creates aUserobject, which is a Spring Security class that implementsUserDetails. This object contains the username, password (which is already encoded), and roles for the user. In this case, the user has a role of"USER".
UserDetails: This is an interface in Spring Security that represents the user's information (like username, password, authorities, etc.) for authentication and authorization purposes.
Role of CustomUserDetailService in Spring Security
Authentication: When a user tries to log in, Spring Security will call the
loadUserByUsernamemethod to fetch the user's details from the database. The returnedUserDetailsobject is then used to authenticate the user.Authorization: Based on the roles assigned to the user (in this case,
"USER"), Spring Security can authorize or deny access to specific resources within the application.
Configuring Spring Security
In this section, we will configure Spring Security to secure the application and control user access. We will do this by customizing the security filter chain and using Spring's authentication manager.
SecurityConfig Code
Here’s the SecurityConfig class where we configure Spring Security for the application:
javaCopyEditpackage com.example.demo.configs;
import com.example.demo.services.CustomUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
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 {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults()) // CSRF protection
.formLogin(Customizer.withDefaults()) // Enable Form-based Authentication
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/signup").permitAll() // Allow access to /signup without authentication
.anyRequest().authenticated() // Secure all other endpoints
);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public static PasswordEncoder passwordEncoder() {
// Use BCryptPasswordEncoder for password encryption
return new BCryptPasswordEncoder();
}
}
7.2 Explanation of SecurityConfig Class
@Configuration: This annotation marks the class as a source of bean definitions. It is used to configure Spring Beans in the context.@EnableWebSecurity: This annotation enables Spring Security in the application. It tells Spring to look for aSecurityConfigclass for security-related configurations.SecurityFilterChainBean:This bean is responsible for configuring the HTTP security of the application.
CSRF Protection:
http.csrf(Customizer.withDefaults())enables Cross-Site Request Forgery (CSRF) protection by default. This is a critical security feature for web applications.Form-Based Authentication:
http.formLogin(Customizer.withDefaults())enables form-based authentication, where users will be required to provide their username and password to access secured endpoints.Authorization Rules:
authorizeHttpRequests(authorize -> authorize...)defines access control rules for HTTP requests:.requestMatchers("/signup").permitAll(): This allows unrestricted access to the/signupendpoint..anyRequest().authenticated(): This restricts access to all other endpoints and requires authentication.
AuthenticationManagerBean:- This bean is responsible for authenticating the user. Spring Security uses this to authenticate the user when they log in.
PasswordEncoderBean:BCryptPasswordEncoder: This bean is used to encrypt the password before storing it in the database and while comparing it during authentication.
We have used
BCryptPasswordEncoderhere because it is one of the most secure password encoding algorithms available in Spring Security.
How Spring Security Filters Requests
Request Flow:
When a request is made, Spring Security intercepts the request and checks the security configurations (e.g., authentication and authorization).
If the request is for a public endpoint like
/signup, it is permitted without authentication.If the request is for any other endpoint, it is secured and requires authentication. Spring Security checks if the user is logged in. If not, it will redirect the user to the login page.
Authentication:
When a user submits their login credentials (username and password), Spring Security uses the
CustomUserDetailServiceto fetch the user’s details from the database.The password is compared using the
PasswordEncoder(in our case,BCryptPasswordEncoder) to ensure the credentials are valid.If the credentials are valid, the user is authenticated and allowed to access the requested resource.
Authorization:
Once authenticated, Spring Security assigns the user roles and checks if they have the necessary permissions to access the requested resource.
In our case, we have a basic role
"USER". You can add more roles if needed, and Spring Security can be configured to allow or deny access to different parts of the application based on roles.
Next Steps
Now that we have configured basic security for your Spring Boot application, here are some potential next steps you can take to enhance the security:
Add JWT Authentication: Implement JSON Web Tokens (JWT) for stateless authentication instead of relying on session-based authentication.
Role-Based Access Control (RBAC): Extend the roles and permissions structure, allowing fine-grained access control for various parts of your application (e.g., allowing only users with the
ADMINrole to access certain pages).Two-Factor Authentication (2FA): Implement an additional layer of security by requiring users to verify their identity via a second factor (such as an OTP).
Rate Limiting: Protect your application against brute-force attacks by adding rate-limiting on endpoints such as login.
Logging and Monitoring: Use Spring Security's logging capabilities to monitor login attempts and failed authentication events. You can also set up alerts to notify you of suspicious activities.

