Spring Boot 3.5.4 Security and MongoDB Authentication Web App (Thymeleaf + Bootstrap)

by Didin J. on Aug 03, 2025 Spring Boot 3.5.4 Security and MongoDB Authentication Web App (Thymeleaf + Bootstrap)

Build a secure Spring Boot 3.5 web app with Thymeleaf, MongoDB, and Spring Security 6. Includes login, registration, and role-based access.

In this fully updated tutorial, you’ll learn how to build a secure login and registration web application using Spring Boot 3.5.4, Spring Security 6, MongoDB, and Thymeleaf for the frontend view layer. We'll implement form-based authentication, user role-based authorization, password hashing with BCrypt, and a clean UI styled with Bootstrap. By the end, you'll have a working full-stack Java web app with modern Spring features and MongoDB integration.

Prerequisites

Make sure you have:

  • Java 21 or 17

  • Maven 3.9+

  • MongoDB running locally or in Docker

  • IDE (IntelliJ, VS Code, etc.)

You can watch the video tutorial on our YouTube channel, featuring the latest Spring Boot version.


Project Setup with Spring Boot 3.5.4

We’ll start by creating a new Spring Boot project using Spring Initializr or your IDE (e.g., IntelliJ IDEA, Eclipse).

✅ Dependencies to Include:

  • Spring Web

  • Spring Security

  • Spring Data MongoDB

  • Thymeleaf

  • Validation (Jakarta Bean Validation)

  • Lombok (optional, for brevity)

Spring Boot 3.5.4, Spring Security 6, and MongoDB Authentication - spring initializr

Generate and then extract the ZIP file to your Project folder.

🔧 pom.xml (if using Maven)

Here’s the trimmed version of your pom.xml with updated dependencies:

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

<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-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>


Application Properties

Edit src/main/resources/application.properties:

spring.application.name=secureweb
spring.data.mongodb.database=springsecuritydb
spring.data.mongodb.uri=mongodb://localhost:27017/springsecuritydb
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

Make sure MongoDB is running locally.


MongoDB User and Role Models

We’ll define two documents:

  • Role: to store user roles (ROLE_USER, ROLE_ADMIN, etc.)

  • User: to store user credentials, roles, and other profile data

Create a new Java package and class files model/Role.java and model/User.java.

🧩 Role.java

package com.djamware.secureweb.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@RequiredArgsConstructor
@Document
public class Role {
    @Id
    private String id;
    @NonNull
    private String name;
}

🧩 User.java

package com.djamware.secureweb.model;

import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@Document
public class User {
    @Id
    private String id;
    private String username;
    private String password;
    private String email;
    private Set<Role> roles;
}

 

Repositories

We’ll use Spring Data MongoDB interfaces. Create a new Java package and interface files repository/RoleRepository.java  and repository/UserRepository.java.

🗃️ RoleRepository.java

package com.djamware.secureweb.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.djamware.secureweb.model.Role;

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

🗃️ UserRepository.java

package com.djamware.secureweb.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.djamware.secureweb.model.User;

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


Spring Security Configuration (Spring Boot 3.5.4 + Spring Security 6)

Create a Java package and class file config/AuthManagerConfig.java.

📁 AuthManagerConfig.java

package com.djamware.secureweb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;

@Configuration
public class AuthManagerConfig {

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
}

Create a Java package and class file config/SecurityConfig.java.

📁 SecurityConfig.java

package com.djamware.secureweb.config;

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.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 {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
                        .anyRequest().authenticated())
                .formLogin(form -> form
                        .loginPage("/login")
                        .defaultSuccessUrl("/", true)
                        .permitAll())
                .logout(logout -> logout
                        .logoutSuccessUrl("/login?logout")
                        .permitAll());

        return http.build();
    }

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


CustomUserDetailsService Implementation

We need to integrate MongoDB with Spring Security by implementing UserDetailsService. Create a new Java package and file service/CustomUserDetailsService.java.

package com.djamware.secureweb.service;

import java.util.stream.Collectors;

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 com.djamware.secureweb.model.Role;
import com.djamware.secureweb.model.User;
import com.djamware.secureweb.repository.UserRepository;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepo) {
        this.userRepository = userRepo;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User appUser = userRepository.findByUsername(username);
        if (appUser == null) {
            throw new UsernameNotFoundException("User not found");
        }

        return new org.springframework.security.core.userdetails.User(
                appUser.getUsername(),
                appUser.getPassword(),
                appUser.getRoles().stream()
                        .map(Role::getName)
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toSet()));
    }
}


Registration and Login Forms with Thymeleaf

📁 src/main/resources/templates/register.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Register</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
    <h2>Register</h2>
    <form th:action="@{/register}" method="post" th:object="${user}">
        <div class="mb-3">
            <label for="username" class="form-label">Username</label>
            <input type="text" th:field="*{username}" class="form-control" id="username" required>
        </div>
        <div class="mb-3">
            <label for="password" class="form-label">Password</label>
            <input type="password" th:field="*{password}" class="form-control" id="password" required>
        </div>
        <button type="submit" class="btn btn-primary">Register</button>
        <a href="/login" class="btn btn-link">Already have an account?</a>
    </form>
</div>
</body>
</html>

📁 src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
    <h2>Login</h2>
    <form th:action="@{/login}" method="post">
        <div th:if="${param.error}" class="alert alert-danger">Invalid username or password.</div>
        <div th:if="${param.logout}" class="alert alert-success">You have been logged out.</div>

        <div class="mb-3">
            <label for="username" class="form-label">Username</label>
            <input type="text" name="username" class="form-control" id="username" required>
        </div>
        <div class="mb-3">
            <label for="password" class="form-label">Password</label>
            <input type="password" name="password" class="form-control" id="password" required>
        </div>
        <button type="submit" class="btn btn-success">Login</button>
        <a href="/register" class="btn btn-link">Don't have an account?</a>
    </form>
</div>
</body>
</html>

With these two forms in place:

  • /register renders the registration form and handles form submission.

  • /login is handled by Spring Security for authentication.

  • We’re using Bootstrap 5 CDN for styling, and Thymeleaf's th:field / th:object bindings.\


User Registration Controller and MongoDB Persistence

✅ Update UserRepository.java

package com.djamware.secureweb.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.djamware.secureweb.model.User;

public interface UserRepository extends MongoRepository<User, String> {
    User findByUsername(String username);

    boolean existsByUsername(String username);
}

✅ Create a new Java interface file service/UserService.java

package com.djamware.secureweb.service;

import com.djamware.secureweb.model.User;

public interface UserService {
    void register(User user);
}

✅ Create a new Java class file service/UserServiceImpl.java

package com.djamware.secureweb.service;

import java.util.Collections;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.djamware.secureweb.model.Role;
import com.djamware.secureweb.model.User;
import com.djamware.secureweb.repository.RoleRepository;
import com.djamware.secureweb.repository.UserRepository;

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final PasswordEncoder passwordEncoder;

    public UserServiceImpl(UserRepository userRepository,
            RoleRepository roleRepository,
            PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void register(User user) {
        if (userRepository.existsByUsername(user.getUsername())) {
            throw new RuntimeException("Username already exists");
        }

        Role userRole = roleRepository.findByName("ROLE_USER");
        if (userRole == null) {
            userRole = new Role("ROLE_USER");
            roleRepository.save(userRole);
        }

        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setRoles(Collections.singleton(userRole));
        userRepository.save(user);
    }
}

✅ Create a new Java class controller/AuthController.java

package com.djamware.secureweb.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.djamware.secureweb.model.User;
import com.djamware.secureweb.service.UserService;

@Controller
public class AuthController {

    private final UserService userService;

    public AuthController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        model.addAttribute("user", new User());
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(@ModelAttribute("user") User user) {
        userService.register(user);
        return "redirect:/login";
    }

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

At this point:

  • New users are registered and persisted in MongoDB.

  • Passwords are encrypted using BCryptPasswordEncoder.

  • Duplicate usernames are rejected.

  • After registration, users are redirected to the login page.


Securing Routes and Creating a Home Page

SecurityConfig.java (Updated for Route Authorization)

package com.djamware.secureweb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import com.djamware.secureweb.service.CustomUserDetailsService;

@Configuration
public class SecurityConfig {

    private final CustomUserDetailsService userDetailsService;

    public SecurityConfig(CustomUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
                        .anyRequest().authenticated())
                .formLogin(form -> form
                        .loginPage("/login")
                        .defaultSuccessUrl("/", true)
                        .permitAll())
                .logout(logout -> logout
                        .logoutSuccessUrl("/login?logout")
                        .permitAll());

        return http.build();
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

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

Explanation:

  • /login and /register are public.

  • All other routes require authentication.

  • After login, users are redirected to / (the home page).

  • Logout redirects to login with a ?logout parameter.

✅ Create a new Java file controller/HomeController.java

package com.djamware.secureweb.controller;

import java.security.Principal;

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

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model, Principal principal) {
        model.addAttribute("username", principal.getName());
        return "index";
    }
}

templates/index.html (Thymeleaf)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home - Spring Boot Security</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
</head>
<body class="container mt-5">
    <h1>Welcome, <span th:text="${username}"></span>!</h1>
    <p>You are logged in successfully.</p>
    <a class="btn btn-danger" th:href="@{/logout}">Logout</a>
</body>
</html>

✅ Optional: CSS Bootstrap file

You can download and place Bootstrap 5 locally in src/main/resources/static/css/bootstrap.min.css, or use CDN in the layout like:

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

Once this is in place:

  • Only logged-in users can access /.

  • Anonymous users are redirected to /login.

  • The home page greets the logged-in user by username.

Creating Login and Registration Templates

These views will allow users to log in and register. They’re styled with Bootstrap for a clean UI and use Thymeleaf syntax for dynamic form handling.

templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
</head>
<body class="container mt-5">
    <h2>Login</h2>

    <div th:if="${param.logout}" class="alert alert-info">
        You have been logged out.
    </div>
    <div th:if="${param.error}" class="alert alert-danger">
        Invalid username or password.
    </div>

    <form th:action="@{/login}" method="post">
        <div class="mb-3">
            <label for="username" class="form-label">Username</label>
            <input type="text" class="form-control" name="username" required autofocus />
        </div>
        <div class="mb-3">
            <label for="password" class="form-label">Password</label>
            <input type="password" class="form-control" name="password" required />
        </div>
        <button class="btn btn-primary" type="submit">Login</button>
        <a class="btn btn-link" th:href="@{/register}">Register</a>
    </form>
</body>
</html>

templates/register.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Register</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
</head>
<body class="container mt-5">
    <h2>Register</h2>

    <div th:if="${error}" class="alert alert-danger" th:text="${error}"></div>

    <form th:action="@{/register}" th:object="${user}" method="post">
        <div class="mb-3">
            <label class="form-label">Username</label>
            <input type="text" class="form-control" th:field="*{username}" required />
        </div>
        <div class="mb-3">
            <label class="form-label">Password</label>
            <input type="password" class="form-control" th:field="*{password}" required />
        </div>
        <button class="btn btn-success" type="submit">Register</button>
        <a class="btn btn-link" th:href="@{/login}">Back to Login</a>
    </form>
</body>
</html>

With these pages:

  • GET /login renders the login form.

  • POST /login is handled by Spring Security.

  • GET /register renders the registration form.

  • POST /register creates a new user and redirects to login.

Testing the Full Flow

Make sure everything is wired up correctly:

🔧 1. Start the Application

Run the application from your IDE or using the command line:

./mvnw spring-boot:run

🌐 2. Visit the App in Browser

Open: http://localhost:8080

You should be redirected to the /login page.

🧪 3. Test Registration

  1. Click “Register”

  2. Fill in a new username and password.

  3. After submission, you should be redirected to the login page.

🔐 4. Test Login

  1. Use the registered credentials.

  2. You’ll be redirected to the / home page (or secured page).

  3. You'll see your username displayed.

🚪 5. Test Logout

Click the Logout link. You’ll be redirected back to the login page with a logout message.

✅ Success Criteria:

  • ⬜ Can register a new user

  • ⬜ User appears in MongoDB with an encrypted password and roles

  • ⬜ Can log in using Spring Security

  • ⬜ Can access protected routes after login

  • ⬜ Logout works correctly

That wraps up the implementation!

Conclusion

In this updated guide, you learned how to build a secure Spring Boot 3.5.4 web application with MongoDB authentication and role-based access control using Spring Security, Thymeleaf, and Bootstrap. We've modernized the 7-year-old original version to reflect the latest Spring ecosystem and best practices, including:

  • Setting up a Spring Boot 3.5.4 project with Spring Security and MongoDB.

  • Creating user and role models with MongoDB repositories.

  • Securing routes using role-based access control.

  • Implementing registration and login functionality.

  • Building Bootstrap-styled Thymeleaf templates.

  • Protecting web routes and handling sessions with logout.

This architecture is ideal for applications that require a lightweight, full-stack Java-based security solution with easy UI integration. You can further expand this project by:

  • Adding remember-me functionality.

  • Enabling CSRF protection with tokens.

  • Supporting email verification or OAuth2 providers.

  • Building an admin dashboard for managing users.

You can get the full source code on our GitHub.

That's just the basics. If you need more in-depth learning about Java and Spring Framework, you can take the following cheap course:

Thanks!