Spring Security et rôles
Notions théoriques
SecurityFilterChain : configurer les règles d'accès
Spring Security 6 configure les règles d'accès via un bean SecurityFilterChain. C'est ici que vous définissez quelles URL nécessitent une authentification et quels rôles sont requis.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // Réservé aux admins
.requestMatchers("/articles/nouveau").authenticated() // Connecté requis
.requestMatchers("/", "/articles", "/inscription", "/connexion").permitAll() // Public
.anyRequest().authenticated() // Tout le reste : connexion requise
)
.formLogin(form -> form
.loginPage("/connexion") // URL du formulaire de connexion personnalisé
.loginProcessingUrl("/connexion") // URL où Spring traite le formulaire (POST)
.defaultSuccessUrl("/articles", true) // Redirection après connexion réussie
.failureUrl("/connexion?erreur") // Redirection après échec
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/deconnexion") // URL pour se déconnecter (POST)
.logoutSuccessUrl("/connexion") // Redirection après déconnexion
.permitAll()
);
return http.build();
}
Symfony security.yaml | Spring Security |
|---|---|
access_control: [{path: '^/admin', roles: ['ROLE_ADMIN']}] | .requestMatchers("/admin/**").hasRole("ADMIN") |
firewalls: main: form_login: login_path: /connexion | .formLogin(form -> form.loginPage("/connexion")) |
firewalls: main: logout: path: /deconnexion | .logout(logout -> logout.logoutUrl("/deconnexion")) |
is_granted('ROLE_ADMIN') dans Twig | sec:authorize="hasRole('ADMIN')" dans Thymeleaf |
Rôles : ROLE_USER et ROLE_ADMIN
Spring Security préfixe automatiquement les rôles avec ROLE_ dans certains contextes. Quand vous utilisez hasRole("ADMIN"), Spring cherche ROLE_ADMIN. Stockez donc les rôles avec le préfixe dans la base :
utilisateur.setRole("ROLE_USER"); // Utilisateur standard
utilisateur.setRole("ROLE_ADMIN"); // Administrateur
@PreAuthorize sur les méthodes
L'annotation @PreAuthorize permet de sécuriser des méthodes individuelles de service ou de contrôleur. Pour l'activer, ajoutez @EnableMethodSecurity sur la configuration :
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Active @PreAuthorize, @PostAuthorize, @Secured
public class SecurityConfig {
// ...
}
Utilisation :
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/articles/{id}")
public String supprimer(@PathVariable Long id) {
articleRepository.deleteById(id);
return "redirect:/articles";
}
@PreAuthorize("hasRole('ADMIN') or #article.auteur.email == authentication.name")
@GetMapping("/articles/{id}/editer")
public String editer(@PathVariable Long id) {
// Accessible par l'admin OU par l'auteur de l'article
}
Thymeleaf Security : affichage conditionnel selon le rôle
Ajoutez la dépendance thymeleaf-extras-springsecurity6 dans pom.xml :
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
Puis dans les templates (namespace sec) :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<!-- Affiché uniquement si l'utilisateur est connecté -->
<div sec:authorize="isAuthenticated()">
Bonjour, <span sec:authentication="name"></span> !
<a th:href="@{/deconnexion}">Se déconnecter</a>
</div>
<!-- Affiché uniquement si l'utilisateur n'est pas connecté -->
<div sec:authorize="isAnonymous()">
<a th:href="@{/connexion}">Se connecter</a>
<a th:href="@{/inscription}">S'inscrire</a>
</div>
<!-- Réservé aux administrateurs -->
<div sec:authorize="hasRole('ADMIN')">
<a th:href="@{/admin/utilisateurs}">Gérer les utilisateurs</a>
</div>
<!-- Bouton visible uniquement par l'auteur ou un admin -->
<div sec:authorize="hasRole('ADMIN') or #authentication.name == ${article.auteur.email}">
<a th:href="@{/articles/{id}/editer(id=${article.id})}">Modifier</a>
</div>
</body>
</html>
Formulaire de connexion personnalisé
@Controller
public class AuthController {
@GetMapping("/connexion")
public String afficherConnexion(@RequestParam(required = false) String erreur,
Model model) {
if (erreur != null) {
model.addAttribute("erreur", "Email ou mot de passe incorrect.");
}
return "auth/connexion";
}
}
Vue templates/auth/connexion.html :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Connexion - MonBlog</title></head>
<body>
<h1>Connexion</h1>
<div th:if="${erreur}" th:text="${erreur}" class="erreur"></div>
<div th:if="${message}" th:text="${message}" class="succes"></div>
<!-- action="/connexion" doit correspondre à loginProcessingUrl -->
<form action="/connexion" method="post">
<!-- Token CSRF généré automatiquement par Thymeleaf avec Spring Security -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div>
<label for="username">Email</label>
<!-- name="username" est attendu par Spring Security par défaut -->
<input type="email" id="username" name="username" />
</div>
<div>
<label for="password">Mot de passe</label>
<input type="password" id="password" name="password" />
</div>
<button type="submit">Se connecter</button>
</form>
<a th:href="@{/inscription}">Pas encore de compte ? S'inscrire</a>
</body>
</html>
Spring Security active la protection CSRF par défaut. Tous les formulaires POST doivent inclure le token CSRF. Avec Thymeleaf, le tag th:action l'injecte automatiquement. Pour un formulaire action statique (sans th:action), ajoutez manuellement <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />.
Logout : déconnexion
Le logout doit se faire en POST (pas GET) pour éviter que des liens externes ne déconnectent l'utilisateur à son insu :
<form th:action="@{/deconnexion}" method="post">
<button type="submit">Se déconnecter</button>
</form>
Exemple pratique
Configuration complète SecurityConfig.java pour MonBlog :
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
.requestMatchers("/", "/articles", "/articles/{id}").permitAll()
.requestMatchers("/inscription", "/connexion").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/connexion")
.loginProcessingUrl("/connexion")
.defaultSuccessUrl("/articles", true)
.failureUrl("/connexion?erreur")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/deconnexion")
.logoutSuccessUrl("/connexion")
.permitAll()
);
return http.build();
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous allez configurer Spring Security pour protéger les routes du projet MonBlog.
Étape 1 — Configurer SecurityFilterChain
Créez la classe SecurityConfig avec les règles d'accès de MonBlog.
L'ordre des requestMatchers est important. Spring Security évalue les règles dans l'ordre et s'arrête à la première correspondance. Placez toujours les règles les plus spécifiques avant les règles générales. anyRequest().authenticated() doit toujours être en dernier.
Étape 2 — Configurer le formulaire de connexion
Avec defaultSuccessUrl("/articles", true), l'utilisateur est toujours redirigé vers /articles après connexion, quelle que soit la page qu'il essayait d'atteindre. Avec false (par défaut), Spring redirige vers la page demandée originalement. Pour un blog, true est plus adapté ; pour une application métier où l'utilisateur doit revenir à sa page de travail, préférez false.
Étape 3 — Affichage conditionnel dans la vue avec sec:authorize
sec:authorize cache uniquement l'élément HTML côté affichage. Un utilisateur malin peut toujours taper directement /admin dans l'URL. La vraie sécurité est dans SecurityFilterChain (.requestMatchers("/admin/**").hasRole("ADMIN")) et/ou @PreAuthorize. Utilisez toujours les deux niveaux : protection côté serveur + masquage côté vue.