Spring Boot 3 Spring Security 6 Tutorial

In this tutorial, we will learn how to secure REST APIs with Spring Boot 3 and Spring Security 6 using Database Authentication.

Prerequisites

  • JDK 17 or later
  • Maven
  • Spring Boot 3.2+
  • An IDE (IntelliJ IDEA, Eclipse, VS Code, etc.)
  • A relational database (H2, MySQL, PostgreSQL, etc.)

Step 1: Set Up the Spring Boot Project

1.1 Generate the Project

Use Spring Initializr to generate a new Spring Boot project with the following configuration:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 3.2.x
  • Dependencies: Spring Web, Spring Security, Spring Data JPA, H2 Database

1.2 Download and Open the Project

Download the generated project, unzip it, and open it in your IDE.

Example Project Structure

my-spring-security-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/security/
│   │   │       └── SecurityApplication.java
│   │   │       └── config/
│   │   │           └── SecurityConfig.java
│   │   │       └── controller/
│   │   │           └── UserController.java
│   │   │       └── model/
│   │   │           └── User.java
│   │   │       └── repository/
│   │   │           └── UserRepository.java
│   │   │       └── service/
│   │   │           └── UserService.java
│   │   └── resources/
│   │       ├── application.properties
│   └── test/
│       └── java/
│           └── com/example/security/
│               └── SecurityApplicationTests.java
├── mvnw
├── mvnw.cmd
├── pom.xml
└── .mvn/
    └── wrapper/
        └── maven-wrapper.properties

Step 2: Configure pom.xml

Ensure your pom.xml file includes the necessary dependencies:

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>security</name>
    <description>Demo project for Spring Security</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </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>

Step 3: Configure the Database

3.1 application.properties

Configure the H2 database in the src/main/resources/application.properties file.

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

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

Step 4: Create the User Entity

Create a User entity in the src/main/java/com/example/security/model directory.

package com.example.security.model;

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

@Entity
@Table(name = "users")
public class User {

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

    private String username;
    private String password;

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "roles", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "role")
    private Set<String> 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<String> getRoles() {
        return roles;
    }

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

Step 5: Create the User Repository

Create a repository interface named UserRepository in the src/main/java/com/example/security/repository directory.

package com.example.security.repository;

import com.example.security.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);
}

Step 6: Create the User Service

Create a service class named UserService in the src/main/java/com/example/security/service directory.

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.authority.SimpleGrantedAuthority;
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;

import java.util.stream.Collectors;

@Service
public class UserService 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: " + username));

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.getRoles().stream()
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList())
        );
    }
}

Step 7: Configure Spring Security

Create a configuration class named SecurityConfig in the src/main/java/com/example/security/config directory.

package com.example.security.config;

import com.example.security.service.UserService;
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.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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
@EnableMethodSecurity
public class SecurityConfig {

    @Autowired
    private UserService userService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic();
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return userService;
    }

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

Step 8: Create the User Controller

Create a REST controller class named UserController in the src/main/java/com/example/security/controller directory.

package com.example.security.controller;

import com.example.security.model.User;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or (hasRole('USER') and #id == principal.id)")
    public Optional<User> getUserById(@PathVariable Long id) {
        return userRepository.findById(id);
    }

    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public User createUser(@RequestBody User user) {
        return userRepository.save(user);
    }

    @PutMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or (hasRole('USER') and #id == principal.id)")
    public Optional<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        return userRepository.findById(id).map(user -> {
            user.setUsername(userDetails.getUsername());
            user.setPassword(userDetails.getPassword());
            user.setRoles(userDetails.getRoles());
            return userRepository.save(user);
        });
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(@PathVariable Long id) {
        userRepository.deleteById(id);
    }
}

Step 9: Running the Application

9.1 Add Initial Data

Create an import.sql file in the src/main/resources directory to add initial data.

INSERT INTO users (username, password) VALUES ('admin', '$2a$10$DowQj5./o6CHrY1V0sBbIu.JhD2z1hcuyxN.k1DSjqPLyDzDlG6Iy'); -- password: admin
INSERT INTO roles (user_id, role) VALUES (1, 'ROLE_ADMIN');

INSERT INTO users (username, password) VALUES ('user', '$2a$10$DowQj5./o6CHrY1V0sBbIu.JhD2z1hcuyxN.k1DSjqPLyDzDlG6Iy'); -- password: user
INSERT INTO roles (user_id, role) VALUES (2, 'ROLE_USER');

9.2 Run the Application

Run the Spring Boot application using your IDE or the command line:

./mvnw spring-boot:run

Step 10: Verify the Application

Use a tool like Postman or curl to test the endpoints with different user roles.

  1. Get All Users (Admin only):

    • URL: http://localhost:8080/users
    • Method: GET
    • Authentication: Basic Auth with admin/admin
  2. Get User by ID (Admin or User):

    • URL: http://localhost:8080/users/1
    • Method: GET
    • Authentication: Basic Auth with admin/admin or user/user
  3. Create User (Admin only):

    • URL: http://localhost:8080/users
    • Method: POST
    • Body:
      {
        "username": "newuser",
        "password": "password",
        "roles": ["ROLE_USER"]
      }
      
    • Authentication: Basic Auth with admin/admin
  4. Update User (Admin or User):

    • URL: http://localhost:8080/users/1
    • Method: PUT
    • Body:
      {
        "username": "updateduser",
        "password": "password",
        "roles": ["ROLE_USER"]
      }
      
    • Authentication: Basic Auth with admin/admin or user/user
  5. Delete User (Admin only):

    • URL: http://localhost:8080/users/1
    • Method: DELETE
    • Authentication: Basic Auth with admin/admin

Following these steps, you have successfully secured your Spring Boot application using Spring Security 6 and Basic Authentication with role-based access control.


Comments