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.
- Authentication: Validating a user's identity (e.g., through login).
- Authorization: Determining access permissions for a user (e.g., based on roles).
- Request Filtering: Allowing or denying access to specific endpoints based on user roles.
- Exception Handling: Managing what happens when a user is denied access.
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>
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
}
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);
}
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())
);
}
}
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);
}
}
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>
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>
-
User Login:
- The user enters credentials.
-
UserDetailsService
loads the user and validates the password. - Spring Security authenticates the user and assigns roles.
-
Request Filtering:
- HTTP requests are matched against role-based rules in
SecurityConfig
. - Unauthorized users are redirected to
/access-denied
.
- HTTP requests are matched against role-based rules in
-
Exception Handling:
- Access denied requests are handled by
accessDeniedPage
.
- Access denied requests are handled by
- Add Sample Users: Add some users with roles in your database (e.g., via H2 Console or initial data).
-
Access Role-Specific Pages:
-
/admin/**
→ Accessible byROLE_ADMIN
. -
/user/**
→ Accessible byROLE_USER
orROLE_ADMIN
.
-
-
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.