Bonjour ! Dans la leçon précédente, nous avons mis en place un système d'authentification robuste en stockant nos utilisateurs et leurs rôles dans une base de données MySQL. Nous avons franchi une étape essentielle en passant d'une gestion en mémoire à une solution persistante.
Aujourd'hui, nous allons nous appuyer sur ce travail pour aborder le concept complémentaire et tout aussi crucial : l'autorisation. Si l'authentification répond à la question "Qui êtes-vous ?", l'autorisation répond à "Qu'avez-vous le droit de faire ?". L'objectif de cette leçon est de vous apprendre à utiliser les rôles que nous avons définis (comme USER ou ADMIN) pour restreindre l'accès à certains de vos endpoints d'API. Par exemple, nous ferons en sorte que seuls les administrateurs puissent consulter la liste de tous les utilisateurs.
1. Qu'est-ce que le Contrôle d'Accès Basé sur les Rôles (RBAC) ?
Le RBAC (Role-Based Access Control) est un modèle de sécurité très répandu qui consiste à attribuer des permissions aux utilisateurs non pas individuellement, mais à travers des rôles.
Pour bien comprendre les concepts, je vous invite à lire l'introduction de l'article suivant qui définit clairement le RBAC et ses avantages.
Sécurisez vos API avec Spring Security : accès par rôle - sfeir.dev
Cet article du blog de SFEIR commence par une excellente définition du RBAC.
Lisez la première partie de l'article, jusqu'à la section "Nos API". Concentrez-vous sur les trois concepts clés : Rôles, Permissions et Attribution.
En résumé, un utilisateur se voit attribuer un ou plusieurs rôles, et chaque rôle dispose d'un ensemble de permissions (droits d'accès).

2. Modélisation des Rôles dans notre Application
Dans la leçon précédente, nous avons utilisé un simple champ String pour le rôle dans notre entité User. C'est fonctionnel, mais pour une application professionnelle, il est préférable de créer une structure plus formelle. Nous allons donc :
- Créer une énumération
RoleEnumpour garantir la cohérence des noms de rôles. - Créer une entité JPA
Rolequi représentera un rôle dans la base de données. - Modifier notre entité
Userpour établir une relation avec l'entitéRole. - Mettre à jour la méthode qui fournit les autorisations à Spring Security.
- Créer un service pour initialiser les rôles dans la base de données au démarrage de l'application.
L'article de SFEIR que vous venez de commencer à lire décrit précisément ces étapes.
Sécurisez vos API avec Spring Security : accès par rôle - sfeir.dev
Cet article vous guidera dans la mise en place d'une gestion des rôles professionnelle.
Reprenez votre lecture à partir de la section "Créons nos rôles" jusqu'à la fin de la section "Les rôles dans le contexte d'authentification". Suivez les étapes pour : Créer l'énumération RoleEnum, l'entité Role et son RoleRepository. Mettre en place le RoleService avec l'annotation @PostConstruct pour initialiser les rôles. Mettre à jour votre entité User pour la lier à Role avec une relation @ManyToOne. Modifier la méthode getAuthorities() dans votre CustomUserDetailsService (ou directement dans votre entité User si elle implémente UserDetails) pour qu'elle retourne le rôle correctement formaté pour Spring Security.
Voici un récapitulatif du code que vous devriez implémenter.
RoleEnum.java
public enum RoleEnum {
USER,
ADMIN
}
Role.java (Entité)
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Enumerated(EnumType.STRING)
@Column(length = 20, unique = true)
private RoleEnum name;
// Getters, Setters, etc.
}
N'oubliez pas de créer le RoleRepository correspondant.
Mise à jour de User.java
// ... dans la classe User
@ManyToOne
@JoinColumn(name = "role_id", nullable = false)
private Role role;
Mise à jour de CustomUserDetailsService.java
La méthode loadUserByUsername doit être adaptée pour récupérer le rôle via la relation. La ligne qui crée les autorités devient :
// ... dans la méthode loadUserByUsername
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().getName().toString()))
);
Enfin, lors de l'inscription (registerUser), vous devrez attribuer un rôle par défaut à chaque nouvel utilisateur, généralement le rôle USER.
3. Sécurisation des Endpoints au Niveau des Méthodes
Maintenant que notre structure de rôles est en place, nous pouvons l'utiliser pour protéger nos endpoints. Spring Security offre une approche très puissante et lisible : la sécurité au niveau des méthodes à l'aide d'annotations.
Étape 1 : Activer la sécurité des méthodes
La première chose à faire est d'indiquer à Spring que nous souhaitons utiliser cette fonctionnalité.
Sécurisez vos API avec Spring Security : accès par rôle - sfeir.dev
L'article explique comment activer cette fonctionnalité.
Lisez la courte section "Mise à jour de la configuration de sécurité" et ajoutez l'annotation @EnableMethodSecurity à votre classe SecurityConfig.java.
Étape 2 : Utiliser @PreAuthorize
L'annotation @PreAuthorize permet d'exécuter une expression SpEL (Spring Expression Language) avant d'entrer dans une méthode. Si l'expression renvoie false, l'accès est refusé et une exception AccessDeniedException est levée.
Les expressions les plus courantes pour le RBAC sont :
@PreAuthorize("hasRole('NOM_DU_ROLE')"): L'utilisateur doit avoir le rôle spécifié.@PreAuthorize("hasAnyRole('ROLE_1', 'ROLE_2')"): L'utilisateur doit avoir au moins l'un des rôles listés.@PreAuthorize("isAuthenticated()"): L'utilisateur doit simplement être authentifié, peu importe son rôle.
La vidéo suivante montre comment mettre en place la sécurité basée sur les rôles et les permissions de manière très concrète. Nous allons nous concentrer sur la partie qui utilise @PreAuthorize.
SPRING BOOT | SPRING SECURITY 6/7 | Rôles et Permissions
La vidéo "Rôles et Permissions" de la chaîne chillotech est un guide pratique très complet sur l'autorisation.
Regardez attentivement cette partie qui montre comment passer de la configuration dans SecurityFilterChain à la sécurité au niveau des méthodes : Motivation : Le présentateur explique pourquoi il est préférable de déplacer la logique d'autorisation des rôles vers les contrôleurs. Implémentation et Test : Il ajoute @EnableMethodSecurity, applique @PreAuthorize("hasAuthority('ROLE_ADMINISTRATEUR')") à une méthode de contrôleur, puis teste le résultat avec Postman, montrant qu'un manager reçoit une erreur 403 (Forbidden).
Créons un exemple concret. Imaginez un UserController avec une méthode pour lister tous les utilisateurs. Nous voulons que seuls les administrateurs puissent y accéder.
UserController.java
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserRepository userRepository;
// ... constructeur
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userRepository.findAll());
}
@GetMapping("/me")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<UserDetails> getMyInfo(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return ResponseEntity.ok(userDetails);
}
}
Remarques importantes :
- Avec
hasRole, vous n'avez pas besoin de préfixer le rôle avecROLE_. Spring s'en charge pour vous. - Si vous utilisez
hasAuthority, vous devez spécifier le nom complet, par exemplehasAuthority('ROLE_ADMIN').
4. Aller plus loin : des Rôles aux Permissions
Le RBAC est puissant, mais dans des applications complexes, on a souvent besoin d'une granularité encore plus fine. On peut imaginer qu'un rôle MANAGER puisse lire les avis (avis:read) et les modifier (avis:update), mais pas les supprimer (avis:delete), tandis qu'un ADMIN a toutes ces permissions. C'est le contrôle d'accès basé sur les permissions.
Un rôle devient alors un simple conteneur pour un ensemble de permissions. Cette approche est extrêmement flexible et professionnelle. La vidéo chillotech montre une implémentation complète de ce concept.
SPRING BOOT | SPRING SECURITY 6/7 | Rôles et Permissions
La vidéo continue en montrant comment implémenter un système de permissions.
Même si vous ne l'implémentez pas immédiatement, regardez comment l'auteur procède : Définition des permissions : Il crée une énumération TypePermission listant des actions granulaires (ex: ADMINISTRATEUR_CREATE, MANAGER_DELETE_AVIS). Association et récupération : Il associe ces permissions aux rôles existants, puis modifie la méthode getAuthorities() pour qu'elle retourne la liste des permissions de l'utilisateur, en plus de son rôle. Utilisation avec @PreAuthorize : Il remplace @PreAuthorize("hasRole('ADMIN')") par @PreAuthorize("hasAuthority('ADMINISTRATEUR_READ')"), ce qui permet un contrôle beaucoup plus fin.
Comprendre cette distinction entre rôles et permissions est un atout majeur pour un développeur backend.
Conclusion
Félicitations ! Vous avez ajouté une couche d'autorisation à votre API, une compétence indispensable pour tout développeur backend. Vous êtes maintenant capable de protéger vos endpoints en fonction des responsabilités de chaque utilisateur.
Points clés à retenir :
- L'autorisation (ce qu'on peut faire) complète l'authentification (qui on est).
- La modélisation des rôles avec une entité JPA dédiée (
Role) et une énumération (RoleEnum) est une bonne pratique. - L'annotation
@EnableMethodSecurityactive la sécurité au niveau des méthodes. @PreAuthorizeest une annotation puissante pour définir des règles d'accès directement sur vos méthodes de contrôleur, en utilisant des expressions commehasRole('...')ouhasAuthority('...').- La distinction entre les rôles (groupes d'utilisateurs) et les permissions (actions spécifiques) permet une gestion des accès encore plus flexible et granulaire.
Dans la prochaine leçon, nous aborderons la gestion centralisée des erreurs. Cela nous sera très utile, par exemple, pour personnaliser la réponse envoyée à un utilisateur qui tente d'accéder à une ressource sans en avoir le droit (erreur 403 Forbidden).