Yes. The next extension should add user registration + login/logout + Spring Security + per-user Todo lists.

The architecture becomes:

Browser
   │
   ▼
Spring Security
   │
   ├── Login
   ├── Logout
   └── Authentication
   │
   ▼
Todo Controller
   │
   ▼
Todo Service
   │
   ├──────────────┐
   ▼              ▼
UserRepository  TodoRepository
   │              │
   └──────┬───────┘
          ▼
        MySQL

I recommend using Spring Security 6 / Spring Boot 3.x, with passwords stored using BCrypt, never as plain text.

I can give you the complete extension code, replacing the previous project with:

  • User entity
  • Todo → User relationship
  • Registration page
  • Login page
  • Logout
  • BCrypt password hashing
  • Spring Security configuration
  • User-specific todos
  • Protected /todos/** pages
  • REST API authentication
  • MySQL database changes
  • Bootstrap UI
  • Complete Eclipse project structure

The important database design will become:

users
────────────────────
id
username
password
role


todos
────────────────────
id
title
completed
user_id  → users.id

So, for example:

User: devesh
   │
   ├── Learn Java
   ├── Learn Spring
   └── Learn MySQL

User: rahul
   │
   ├── Learn Python
   └── Learn Django

Each logged-in user will see only their own todos.

If you want, I can provide the full copy-paste code for every Java, HTML, pom.xml, and application.properties file as the next step.

Yes. Below is the complete upgraded Spring Todo Web Application with Login/Security.

It extends the previous project with:

  • Spring Security
  • Registration
  • Login/logout
  • BCrypt password hashing
  • User roles
  • MySQL
  • User-specific Todo lists
  • Add/edit/delete/toggle Todo
  • Thymeleaf + Bootstrap
  • REST API
  • Validation

1. Final project structure

todo/
├── pom.xml
│
└── src/main/
    ├── java/com/example/todo/
    │
    │   ├── TodoApplication.java
    │   │
    │   ├── config/
    │   │   └── SecurityConfig.java
    │   │
    │   ├── controller/
    │   │   ├── AuthController.java
    │   │   ├── TodoController.java
    │   │   └── TodoRestController.java
    │   │
    │   ├── model/
    │   │   ├── User.java
    │   │   └── Todo.java
    │   │
    │   ├── repository/
    │   │   ├── UserRepository.java
    │   │   └── TodoRepository.java
    │   │
    │   └── service/
    │       ├── UserService.java
    │       └── TodoService.java
    │
    └── resources/
        ├── templates/
        │   ├── login.html
        │   ├── register.html
        │   ├── index.html
        │   └── edit.html
        │
        └── application.properties

2. MySQL database

Start MySQL:

sudo systemctl start mysql

Open MySQL:

sudo mysql

Create database and user:

CREATE DATABASE todo_db;

CREATE USER 'todo_user'@'localhost'
IDENTIFIED BY 'todo_password';

GRANT ALL PRIVILEGES ON todo_db.*
TO 'todo_user'@'localhost';

FLUSH PRIVILEGES;

EXIT;

You don’t need to manually create the users or todo tables.

JPA/Hibernate will create them.


3. pom.xml

Replace your existing pom.xml:

<?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>3.5.6</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>

    <artifactId>todo</artifactId>

    <version>0.0.1-SNAPSHOT</version>

    <name>todo</name>

    <description>
        Spring Boot Todo Application with Security
    </description>

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

    <dependencies>

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

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

        <!-- Thymeleaf Spring Security -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
        </dependency>

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

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

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- DevTools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Security Testing -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-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>

4. Main application

TodoApplication.java

package com.example.todo;

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

@SpringBootApplication
public class TodoApplication {

    public static void main(String[] args) {

        SpringApplication.run(
            TodoApplication.class,
            args
        );

    }
}

5. User entity

Create:

model/User.java
package com.example.todo.model;

import jakarta.persistence.*;

import java.util.ArrayList;
import java.util.List;

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

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

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String role = "USER";

    @OneToMany(
        mappedBy = "user",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<Todo> todos = new ArrayList<>();

    public User() {
    }

    public User(
            String username,
            String password,
            String role) {

        this.username = username;
        this.password = password;
        this.role = role;
    }

    public Long getId() {
        return 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 String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public List<Todo> getTodos() {
        return todos;
    }

    public void setTodos(List<Todo> todos) {
        this.todos = todos;
    }
}

6. Todo entity

Replace the previous Todo.java with:

package com.example.todo.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Entity
@Table(name = "todos")
public class Todo {

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

    @NotBlank(message = "Title is required")
    @Size(
        min = 3,
        max = 100,
        message = "Title must be between 3 and 100 characters"
    )
    private String title;

    private boolean completed;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    public Todo() {
    }

    public Todo(String title) {

        this.title = title;

        this.completed = false;
    }

    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

7. User repository

repository/UserRepository.java

package com.example.todo.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.todo.model.User;

public interface UserRepository
        extends JpaRepository<User, Long> {

    Optional<User> findByUsername(String username);

    boolean existsByUsername(String username);
}

8. Todo repository

repository/TodoRepository.java

package com.example.todo.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.todo.model.Todo;
import com.example.todo.model.User;

public interface TodoRepository
        extends JpaRepository<Todo, Long> {

    List<Todo> findByUser(User user);

    java.util.Optional<Todo>
    findByIdAndUser(Long id, User user);
}

This is important.

Instead of:

findAll()

we use:

findByUser(user)

Therefore one user cannot see another user’s todos.


9. User service

Create:

service/UserService.java
package com.example.todo.service;

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

import com.example.todo.model.User;
import com.example.todo.repository.UserRepository;

@Service
public class UserService {

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    public UserService(
            UserRepository userRepository,
            PasswordEncoder passwordEncoder) {

        this.userRepository = userRepository;

        this.passwordEncoder = passwordEncoder;
    }

    public User registerUser(
            String username,
            String password) {

        if (userRepository.existsByUsername(username)) {

            throw new RuntimeException(
                "Username already exists"
            );

        }

        String encodedPassword =
                passwordEncoder.encode(password);

        User user = new User(
            username,
            encodedPassword,
            "USER"
        );

        return userRepository.save(user);
    }
}

10. Todo service

Replace the previous service:

service/TodoService.java

with:

package com.example.todo.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.example.todo.model.Todo;
import com.example.todo.model.User;
import com.example.todo.repository.TodoRepository;

@Service
public class TodoService {

    private final TodoRepository todoRepository;

    public TodoService(
            TodoRepository todoRepository) {

        this.todoRepository = todoRepository;
    }

    public List<Todo> getTodosForUser(User user) {

        return todoRepository.findByUser(user);
    }

    public Todo getTodo(
            Long id,
            User user) {

        return todoRepository
                .findByIdAndUser(id, user)
                .orElseThrow(() ->
                    new RuntimeException(
                        "Todo not found"
                    )
                );
    }

    public Todo saveTodo(
            Todo todo,
            User user) {

        todo.setUser(user);

        return todoRepository.save(todo);
    }

    public void updateTodo(
            Long id,
            Todo details,
            User user) {

        Todo todo = getTodo(id, user);

        todo.setTitle(details.getTitle());

        todo.setCompleted(
            details.isCompleted()
        );

        todoRepository.save(todo);
    }

    public void deleteTodo(
            Long id,
            User user) {

        Todo todo = getTodo(id, user);

        todoRepository.delete(todo);
    }

    public void toggleTodo(
            Long id,
            User user) {

        Todo todo = getTodo(id, user);

        todo.setCompleted(
            !todo.isCompleted()
        );

        todoRepository.save(todo);
    }
}

11. Spring Security configuration

This is the most important new class.

Create:

config/SecurityConfig.java
package com.example.todo.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http) throws Exception {

        http

            .authorizeHttpRequests(auth -> auth

                .requestMatchers(
                    "/login",
                    "/register",
                    "/css/**",
                    "/js/**"
                ).permitAll()

                .requestMatchers("/api/**")
                .authenticated()

                .anyRequest()
                .authenticated()
            )

            .formLogin(form -> form

                .loginPage("/login")

                .defaultSuccessUrl(
                    "/",
                    true
                )

                .permitAll()
            )

            .logout(logout -> logout

                .logoutSuccessUrl(
                    "/login?logout"
                )

                .permitAll()
            );

        return http.build();
    }
}

12. Custom UserDetailsService

Spring Security needs to know how to load users from MySQL.

Create:

service/CustomUserDetailsService.java
package com.example.todo.service;

import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;

import com.example.todo.model.User;
import com.example.todo.repository.UserRepository;

@Service
public class CustomUserDetailsService
        implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(
            UserRepository userRepository) {

        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(
            String username)
            throws UsernameNotFoundException {

        User user =
            userRepository
                .findByUsername(username)
                .orElseThrow(() ->
                    new UsernameNotFoundException(
                        "User not found"
                    )
                );

        return User.withUsername(
                user.getUsername()
            )
            .password(user.getPassword())
            .roles(user.getRole())
            .build();
    }
}

13. Authentication controller

Create:

controller/AuthController.java
package com.example.todo.controller;

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

import com.example.todo.service.UserService;

@Controller
public class AuthController {

    private final UserService userService;

    public AuthController(
            UserService userService) {

        this.userService = userService;
    }

    @GetMapping("/login")
    public String login(
            @RequestParam(
                value = "error",
                required = false
            )
            String error,

            @RequestParam(
                value = "logout",
                required = false
            )
            String logout,

            Model model) {

        if (error != null) {

            model.addAttribute(
                "error",
                "Invalid username or password"
            );
        }

        if (logout != null) {

            model.addAttribute(
                "message",
                "You have been logged out"
            );
        }

        return "login";
    }

    @GetMapping("/register")
    public String registerPage(
            Model model) {

        model.addAttribute(
            "username",
            ""
        );

        return "register";
    }

    @PostMapping("/register")
    public String register(
            @RequestParam String username,
            @RequestParam String password,
            Model model) {

        if (username == null ||
            username.trim().isEmpty()) {

            model.addAttribute(
                "error",
                "Username is required"
            );

            return "register";
        }

        if (password == null ||
            password.length() < 6) {

            model.addAttribute(
                "error",
                "Password must contain at least 6 characters"
            );

            return "register";
        }

        try {

            userService.registerUser(
                username.trim(),
                password
            );

            return "redirect:/login?registered";

        } catch (RuntimeException e) {

            model.addAttribute(
                "error",
                e.getMessage()
            );

            return "register";
        }
    }
}

14. Todo web controller

Replace your previous TodoController.java with:

package com.example.todo.controller;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import com.example.todo.model.Todo;
import com.example.todo.model.User;
import com.example.todo.repository.UserRepository;
import com.example.todo.service.TodoService;

import jakarta.validation.Valid;

@Controller
public class TodoController {

    private final TodoService todoService;

    private final UserRepository userRepository;

    public TodoController(
            TodoService todoService,
            UserRepository userRepository) {

        this.todoService = todoService;

        this.userRepository = userRepository;
    }

    private User getCurrentUser(
            Authentication authentication) {

        return userRepository
                .findByUsername(
                    authentication.getName()
                )
                .orElseThrow();
    }

    @GetMapping("/")
    public String home(
            Model model,
            Authentication authentication) {

        User user =
            getCurrentUser(authentication);

        model.addAttribute(
            "todos",
            todoService.getTodosForUser(user)
        );

        model.addAttribute(
            "todo",
            new Todo()
        );

        model.addAttribute(
            "username",
            user.getUsername()
        );

        return "index";
    }

    @PostMapping("/todos")
    public String addTodo(
            @Valid @ModelAttribute("todo")
            Todo todo,

            BindingResult result,

            Authentication authentication,

            Model model) {

        User user =
            getCurrentUser(authentication);

        if (result.hasErrors()) {

            model.addAttribute(
                "todos",
                todoService.getTodosForUser(user)
            );

            model.addAttribute(
                "username",
                user.getUsername()
            );

            return "index";
        }

        todoService.saveTodo(
            todo,
            user
        );

        return "redirect:/";
    }

    @GetMapping("/todos/edit/{id}")
    public String editTodo(
            @PathVariable Long id,
            Authentication authentication,
            Model model) {

        User user =
            getCurrentUser(authentication);

        Todo todo =
            todoService.getTodo(id, user);

        model.addAttribute(
            "todo",
            todo
        );

        return "edit";
    }

    @PostMapping("/todos/update/{id}")
    public String updateTodo(
            @PathVariable Long id,

            @Valid @ModelAttribute("todo")
            Todo todo,

            BindingResult result,

            Authentication authentication) {

        if (result.hasErrors()) {

            return "edit";
        }

        User user =
            getCurrentUser(authentication);

        todoService.updateTodo(
            id,
            todo,
            user
        );

        return "redirect:/";
    }

    @GetMapping("/todos/delete/{id}")
    public String deleteTodo(
            @PathVariable Long id,
            Authentication authentication) {

        User user =
            getCurrentUser(authentication);

        todoService.deleteTodo(
            id,
            user
        );

        return "redirect:/";
    }

    @GetMapping("/todos/toggle/{id}")
    public String toggleTodo(
            @PathVariable Long id,
            Authentication authentication) {

        User user =
            getCurrentUser(authentication);

        todoService.toggleTodo(
            id,
            user
        );

        return "redirect:/";
    }
}

15. REST controller

Create:

controller/TodoRestController.java
package com.example.todo.controller;

import java.util.List;

import org.springframework.http.*;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import com.example.todo.model.Todo;
import com.example.todo.model.User;
import com.example.todo.repository.UserRepository;
import com.example.todo.service.TodoService;

@RestController
@RequestMapping("/api/todos")
public class TodoRestController {

    private final TodoService todoService;

    private final UserRepository userRepository;

    public TodoRestController(
            TodoService todoService,
            UserRepository userRepository) {

        this.todoService = todoService;

        this.userRepository = userRepository;
    }

    private User currentUser(
            Authentication authentication) {

        return userRepository
                .findByUsername(
                    authentication.getName()
                )
                .orElseThrow();
    }

    @GetMapping
    public List<Todo> getTodos(
            Authentication authentication) {

        User user =
            currentUser(authentication);

        return todoService.getTodosForUser(user);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Todo> getTodo(
            @PathVariable Long id,
            Authentication authentication) {

        try {

            User user =
                currentUser(authentication);

            return ResponseEntity.ok(
                todoService.getTodo(id, user)
            );

        } catch (RuntimeException e) {

            return ResponseEntity
                    .notFound()
                    .build();
        }
    }

    @PostMapping
    public ResponseEntity<Todo> createTodo(
            @RequestBody Todo todo,
            Authentication authentication) {

        User user =
            currentUser(authentication);

        Todo saved =
            todoService.saveTodo(
                todo,
                user
            );

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(saved);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Todo> updateTodo(
            @PathVariable Long id,
            @RequestBody Todo todo,
            Authentication authentication) {

        try {

            User user =
                currentUser(authentication);

            todoService.updateTodo(
                id,
                todo,
                user
            );

            return ResponseEntity.ok(
                todoService.getTodo(id, user)
            );

        } catch (RuntimeException e) {

            return ResponseEntity
                    .notFound()
                    .build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTodo(
            @PathVariable Long id,
            Authentication authentication) {

        try {

            User user =
                currentUser(authentication);

            todoService.deleteTodo(
                id,
                user
            );

            return ResponseEntity
                    .noContent()
                    .build();

        } catch (RuntimeException e) {

            return ResponseEntity
                    .notFound()
                    .build();
        }
    }

    @PatchMapping("/{id}/toggle")
    public ResponseEntity<Todo> toggleTodo(
            @PathVariable Long id,
            Authentication authentication) {

        try {

            User user =
                currentUser(authentication);

            todoService.toggleTodo(
                id,
                user
            );

            return ResponseEntity.ok(
                todoService.getTodo(id, user)
            );

        } catch (RuntimeException e) {

            return ResponseEntity
                    .notFound()
                    .build();
        }
    }
}

16. Login page

Create:

src/main/resources/templates/login.html
<!DOCTYPE html>
<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport"
          content="width=device-width, initial-scale=1">

    <title>Login - Todo</title>

    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
        rel="stylesheet">

</head>

<body class="bg-light">

<div class="container mt-5">

    <div class="row justify-content-center">

        <div class="col-md-5">

            <div class="card shadow">

                <div class="card-header
                            bg-primary
                            text-white">

                    <h3 class="text-center">
                        Todo Login
                    </h3>

                </div>

                <div class="card-body">

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

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

                    <div
                        th:if="${param.registered}"
                        class="alert alert-success">

                        Registration successful.
                        Please login.

                    </div>

                    <form
                        action="/login"
                        method="post">

                        <div class="mb-3">

                            <label class="form-label">
                                Username
                            </label>

                            <input
                                type="text"
                                name="username"
                                class="form-control"
                                required>

                        </div>

                        <div class="mb-3">

                            <label class="form-label">
                                Password
                            </label>

                            <input
                                type="password"
                                name="password"
                                class="form-control"
                                required>

                        </div>

                        <button
                            type="submit"
                            class="btn btn-primary w-100">

                            Login

                        </button>

                    </form>

                    <hr>

                    <div class="text-center">

                        <span>
                            Don't have an account?
                        </span>

                        <a href="/register">
                            Register
                        </a>

                    </div>

                </div>

            </div>

        </div>

    </div>

</div>

</body>

</html>

17. Registration page

Create:

src/main/resources/templates/register.html
<!DOCTYPE html>
<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport"
          content="width=device-width, initial-scale=1">

    <title>Register - Todo</title>

    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
        rel="stylesheet">

</head>

<body class="bg-light">

<div class="container mt-5">

    <div class="row justify-content-center">

        <div class="col-md-5">

            <div class="card shadow">

                <div class="card-header
                            bg-success
                            text-white">

                    <h3 class="text-center">
                        Create Account
                    </h3>

                </div>

                <div class="card-body">

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

                    <form
                        action="/register"
                        method="post">

                        <div class="mb-3">

                            <label class="form-label">
                                Username
                            </label>

                            <input
                                type="text"
                                name="username"
                                class="form-control"
                                minlength="3"
                                required>

                        </div>

                        <div class="mb-3">

                            <label class="form-label">
                                Password
                            </label>

                            <input
                                type="password"
                                name="password"
                                class="form-control"
                                minlength="6"
                                required>

                            <small class="text-muted">
                                Minimum 6 characters
                            </small>

                        </div>

                        <button
                            type="submit"
                            class="btn btn-success w-100">

                            Register

                        </button>

                    </form>

                    <hr>

                    <div class="text-center">

                        Already have an account?

                        <a href="/login">
                            Login
                        </a>

                    </div>

                </div>

            </div>

        </div>

    </div>

</div>

</body>

</html>

18. Todo home page

Create/replace:

src/main/resources/templates/index.html
<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <meta name="viewport"
          content="width=device-width, initial-scale=1">

    <title>My Todos</title>

    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
        rel="stylesheet">

</head>

<body class="bg-light">

<nav class="navbar navbar-dark bg-primary">

    <div class="container">

        <span class="navbar-brand">
            Todo Application
        </span>

        <div class="d-flex align-items-center">

            <span class="text-white me-3">

                Welcome,
                <strong th:text="${username}">
                    user
                </strong>

            </span>

            <form
                action="/logout"
                method="post">

                <button
                    type="submit"
                    class="btn btn-light btn-sm">

                    Logout

                </button>

            </form>

        </div>

    </div>

</nav>


<div class="container mt-5">

    <div class="row justify-content-center">

        <div class="col-md-10">

            <div class="card shadow">

                <div class="card-header">

                    <h3 class="mb-0">
                        My Todo List
                    </h3>

                </div>

                <div class="card-body">


                    <!-- ADD TODO -->

                    <form
                        th:action="@{/todos}"
                        th:object="${todo}"
                        method="post"
                        class="mb-4">

                        <div class="input-group">

                            <input
                                type="text"
                                th:field="*{title}"
                                class="form-control"
                                placeholder="Enter a new todo">

                            <button
                                type="submit"
                                class="btn btn-primary">

                                Add Todo

                            </button>

                        </div>

                        <div
                            th:if="${#fields.hasErrors('title')}"
                            th:errors="*{title}"
                            class="text-danger mt-2">

                        </div>

                    </form>


                    <!-- TODO TABLE -->

                    <table class="table table-hover">

                        <thead class="table-dark">

                        <tr>

                            <th>ID</th>

                            <th>Todo</th>

                            <th>Status</th>

                            <th>Actions</th>

                        </tr>

                        </thead>

                        <tbody>

                        <tr th:each="todo : ${todos}">

                            <td th:text="${todo.id}">
                                1
                            </td>

                            <td>

                                <span
                                    th:text="${todo.title}"
                                    th:classappend="${todo.completed}
                                    ? 'text-decoration-line-through text-muted'
                                    : ''">

                                    Todo

                                </span>

                            </td>

                            <td>

                                <span
                                    th:if="${todo.completed}"
                                    class="badge bg-success">

                                    Completed

                                </span>

                                <span
                                    th:unless="${todo.completed}"
                                    class="badge bg-warning text-dark">

                                    Pending

                                </span>

                            </td>

                            <td>

                                <a
                                    th:href="@{/todos/toggle/{id}(id=${todo.id})}"
                                    class="btn btn-sm btn-success">

                                    Toggle

                                </a>

                                <a
                                    th:href="@{/todos/edit/{id}(id=${todo.id})}"
                                    class="btn btn-sm btn-primary">

                                    Edit

                                </a>

                                <a
                                    th:href="@{/todos/delete/{id}(id=${todo.id})}"
                                    class="btn btn-sm btn-danger"
                                    onclick="return confirm('Delete this todo?');">

                                    Delete

                                </a>

                            </td>

                        </tr>


                        <tr
                            th:if="${#lists.isEmpty(todos)}">

                            <td
                                colspan="4"
                                class="text-center text-muted">

                                You don't have any todos yet.

                            </td>

                        </tr>

                        </tbody>

                    </table>

                </div>

            </div>

        </div>

    </div>

</div>

</body>

</html>

19. Edit page

Create:

src/main/resources/templates/edit.html
<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org">

<head>

    <meta charset="UTF-8">

    <meta name="viewport"
          content="width=device-width, initial-scale=1">

    <title>Edit Todo</title>

    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
        rel="stylesheet">

</head>

<body class="bg-light">

<div class="container mt-5">

    <div class="row justify-content-center">

        <div class="col-md-6">

            <div class="card shadow">

                <div class="card-header bg-primary text-white">

                    <h3 class="mb-0">
                        Edit Todo
                    </h3>

                </div>

                <div class="card-body">

                    <form
                        th:action="@{/todos/update/{id}(id=${todo.id})}"
                        th:object="${todo}"
                        method="post">

                        <div class="mb-3">

                            <label class="form-label">
                                Todo
                            </label>

                            <input
                                type="text"
                                th:field="*{title}"
                                class="form-control">

                            <div
                                th:if="${#fields.hasErrors('title')}"
                                th:errors="*{title}"
                                class="text-danger">

                            </div>

                        </div>

                        <div class="form-check mb-3">

                            <input
                                type="checkbox"
                                th:field="*{completed}"
                                class="form-check-input">

                            <label class="form-check-label">
                                Completed
                            </label>

                        </div>

                        <button
                            type="submit"
                            class="btn btn-primary">

                            Update

                        </button>

                        <a
                            href="/"
                            class="btn btn-secondary">

                            Cancel

                        </a>

                    </form>

                </div>

            </div>

        </div>

    </div>

</div>

</body>

</html>

20. application.properties

Use:

spring.application.name=todo

# ==========================================
# MySQL
# ==========================================

spring.datasource.url=jdbc:mysql://localhost:3306/todo_db

spring.datasource.username=todo_user

spring.datasource.password=todo_password


# ==========================================
# JPA / Hibernate
# ==========================================

spring.jpa.hibernate.ddl-auto=update

spring.jpa.show-sql=true

spring.jpa.properties.hibernate.format_sql=true


# ==========================================
# Thymeleaf
# ==========================================

spring.thymeleaf.cache=false


# ==========================================
# Server
# ==========================================

server.port=8080

21. Update Maven project in Eclipse

After changing pom.xml:

Right click project
       ↓
Maven
       ↓
Update Project
       ↓
Check your project
       ↓
OK

Or from terminal:

./mvnw clean install

Then:

./mvnw spring-boot:run

22. Start the application

Make sure MySQL is running:

sudo systemctl status mysql

Then:

./mvnw spring-boot:run

Open:

http://localhost:8080

Because the application is secured, you’ll be redirected to:

/login

23. Register a user

Open:

http://localhost:8080/register

Create:

Username: devesh
Password: password123

Click Register.

You’ll be redirected to login.


24. Login

Enter:

Username: devesh
Password: password123

After successful authentication:

/login
   ↓
Spring Security
   ↓
/
   ↓
Todo Application

You should see:

+------------------------------------------------+
| Todo Application              Welcome, devesh |
|                                  [ Logout ]    |
+------------------------------------------------+
| My Todo List                                   |
|                                                |
| [ Learn Spring Boot             ] [Add Todo]   |
|                                                |
| ID | Todo              | Status | Actions      |
|----|-------------------|--------|--------------|
| 1  | Learn Java        | Pending| Toggle Edit  |
| 2  | Learn Spring Boot | Done   | Toggle Edit  |
+------------------------------------------------+

25. Test multi-user security

This is an important part of the project.

Create:

User 1:
username = devesh
password = password123

Add:

Learn Java
Learn Spring Boot
Learn MySQL

Logout.

Register:

User 2:
username = rahul
password = password123

Login as rahul.

You should see:

My Todo List

No todos yet.

Rahul cannot see Devesh’s todos.

The reason is this repository method:

findByIdAndUser(id, user)

and:

findByUser(user)

This is much safer than simply doing:

findAll()

26. What happens to the password?

Suppose the user registers:

password123

We do:

passwordEncoder.encode(password);

The database does not store:

password123

Instead, it stores a BCrypt hash similar to:

$2a$10$...

You can inspect it:

USE todo_db;

SELECT id, username, password, role
FROM users;

You’ll see something like:

+----+----------+----------------------+------+
| id | username | password             | role |
+----+----------+----------------------+------+
|  1 | devesh   | $2a$10$.............. | USER |
+----+----------+----------------------+------+

Never store real passwords directly in your database.


27. Database relationship

Hibernate creates approximately:

users
--------------------------------
id          BIGINT PRIMARY KEY
username    VARCHAR
password    VARCHAR
role        VARCHAR

and:

todos
--------------------------------
id          BIGINT PRIMARY KEY
title       VARCHAR
completed   BOOLEAN
user_id     BIGINT

Relationship:

        users
          |
          | 1
          |
          | *
          v
        todos

In Java:

@OneToMany
private List<Todo> todos;

and:

@ManyToOne
private User user;

28. REST API

The REST API is also protected.

GET     /api/todos
GET     /api/todos/1
POST    /api/todos
PUT     /api/todos/1
PATCH   /api/todos/1/toggle
DELETE  /api/todos/1

You need to be authenticated to access these endpoints.

For example:

curl http://localhost:8080/api/todos

Without authentication, Spring Security will reject the request.


29. Test the API after login

For a browser-based form login, testing the REST API with curl is slightly different because the authentication is session-based.

For development, you can use Postman:

POST /login

with:

username=devesh
password=password123

Then retain the session cookie and call:

GET /api/todos

For a production REST application, I would normally change this architecture to JWT authentication rather than using the browser session.


30. Important Spring Security flow

The login flow is:

                    Browser
                       |
                       | POST /login
                       v
              Spring Security
                       |
                       v
        CustomUserDetailsService
                       |
                       v
                UserRepository
                       |
                       v
                    MySQL
                       |
                       v
             PasswordEncoder
                       |
                       v
                 BCrypt check
                       |
              ┌────────┴────────┐
              |                 |
           SUCCESS            FAILURE
              |                 |
              v                 v
             "/"             /login?error

The important code is:

@Service
public class CustomUserDetailsService
        implements UserDetailsService

Spring Security calls:

loadUserByUsername()

which loads the user from MySQL.

Then BCrypt verifies the password.


31. Important security rule

Do not do this:

user.setPassword(password);

Instead:

user.setPassword(
    passwordEncoder.encode(password)
);

And don’t create a database column containing plain-text passwords.


32. Run and verify

Use these commands:

sudo systemctl start mysql
cd ~/todo
./mvnw clean
./mvnw spring-boot:run

Then:

http://localhost:8080/register

Test this sequence:

Register
   ↓
Login
   ↓
Add Todo
   ↓
Edit Todo
   ↓
Toggle Todo
   ↓
Delete Todo
   ↓
Logout

Then create a second account and verify that the first user’s todos aren’t visible.


Final architecture

Your application now looks like:

                     ┌───────────────┐
                     │    Browser    │
                     └───────┬───────┘
                             │
                             ▼
                  ┌────────────────────┐
                  │   Spring Security  │
                  └─────────┬──────────┘
                            │
                 ┌──────────┴──────────┐
                 │                     │
                 ▼                     ▼
             Login/Auth            TodoController
                 │                     │
                 ▼                     ▼
             UserService          TodoService
                 │                     │
                 ▼                     ▼
           UserRepository       TodoRepository
                 │                     │
                 └──────────┬──────────┘
                            │
                            ▼
                         MySQL

This is now a solid Spring Boot MVC + Security + JPA + MySQL CRUD application. The natural next production-level extension is role-based authorization (USER/ADMIN), admin user management, BCrypt password validation, CSRF-safe delete forms, and JWT-based REST authentication.