MVC Security - anastasiamexa/springboot-tutorial GitHub Wiki

Spring Boot MVC Security focuses on securing your web application, primarily using Spring Security, a powerful framework for authentication, authorization, and protecting against common vulnerabilities like CSRF and session fixation. Below is a comprehensive explanation of how to implement security, handle user roles, and manage exceptions.

Key Features of Spring Security

  1. Authentication: Validating a user's identity (e.g., through login).
  2. Authorization: Determining access permissions for a user (e.g., based on roles).
  3. Request Filtering: Allowing or denying access to specific endpoints based on user roles.
  4. Exception Handling: Managing what happens when a user is denied access.

Setting Up Spring Security in Spring Boot MVC

Add the Spring Security starter dependency to your pom.xml:

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

Implementation: Login, Role-Based Access, and Exception Handling

1. User Entity with Roles

Define a User entity with roles.

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

@Entity
public class User {

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

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "role")
    private Set<String> roles; // e.g., ["ROLE_ADMIN", "ROLE_USER"]

    // Getters and Setters
}

2. Repository for User

Create a repository to retrieve users by username.

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

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

3. UserDetailsService Implementation

Integrate your User entity with Spring Security’s UserDetailsService to load users for authentication.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

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

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

4. Security Configuration

Set up Spring Security with role-based access control.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.csrf().disable()
            .authorizeHttpRequests()
                .requestMatchers("/admin/**").hasRole("ADMIN") // Admin-only access
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // User or Admin access
                .anyRequest().permitAll() // Public access
            .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard", true)
                .permitAll()
            .and()
            .exceptionHandling()
                .accessDeniedPage("/access-denied"); // Custom access-denied page

        return http.build();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth, CustomUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
}

5. Login and Exception Handling

Login Page (login.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <label>Username:</label>
        <input type="text" name="username" /><br/>
        <label>Password:</label>
        <input type="password" name="password" /><br/>
        <button type="submit">Login</button>
    </form>
</body>
</html>

Access Denied Page (access-denied.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Access Denied</title>
</head>
<body>
    <h1>Access Denied</h1>
    <p>You do not have permission to access this page.</p>
    <a href="/">Go back to Home</a>
</body>
</html>

6. Thymeleaf Views with Role-Based Menus

Use Thymeleaf to dynamically display menus based on roles.

Example (dashboard.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Dashboard</title>
</head>
<body>
    <h1>Welcome to the Dashboard</h1>

    <div>
        <a th:href="@{/user/profile}" th:if="${#authorization.expression('hasRole(\'ROLE_USER\')')}">User Profile</a>
        <a th:href="@{/admin/manage}" th:if="${#authorization.expression('hasRole(\'ROLE_ADMIN\')')}">Admin Panel</a>
        <a th:href="@{/logout}">Logout</a>
    </div>
</body>
</html>

Flow of Spring Boot Security with Roles

  1. User Login:
    • The user enters credentials.
    • UserDetailsService loads the user and validates the password.
    • Spring Security authenticates the user and assigns roles.
  2. Request Filtering:
    • HTTP requests are matched against role-based rules in SecurityConfig.
    • Unauthorized users are redirected to /access-denied.
  3. Exception Handling:
    • Access denied requests are handled by accessDeniedPage.

Testing the Application

  1. Add Sample Users: Add some users with roles in your database (e.g., via H2 Console or initial data).
  2. Access Role-Specific Pages:
    • /admin/** → Accessible by ROLE_ADMIN.
    • /user/** → Accessible by ROLE_USER or ROLE_ADMIN.
  3. Test Scenarios:
    • Try logging in with valid and invalid credentials.
    • Access restricted pages without login or with insufficient roles.

This setup secures your Spring Boot MVC application with role-based access control, custom login, and exception handling while using Thymeleaf for dynamic views.

⚠️ **GitHub.com Fallback** ⚠️