Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 75

Gestion des Accès par Rôles (RBAC)

Recommended resources

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).

Ce diagramme illustre le principe du RBAC. Des utilisateurs sont associés à des rôles (ADMIN, STAFF, USER), et chaque rôle est lié à des privilèges (DELETE, WRITE, READ). Un utilisateur hérite des privilèges des rôles qui lui sont attribué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 :

  1. Créer une énumération RoleEnum pour garantir la cohérence des noms de rôles.
  2. Créer une entité JPA Role qui représentera un rôle dans la base de données.
  3. Modifier notre entité User pour établir une relation avec l'entité Role.
  4. Mettre à jour la méthode qui fournit les autorisations à Spring Security.
  5. 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 avec ROLE_. Spring s'en charge pour vous.
  • Si vous utilisez hasAuthority, vous devez spécifier le nom complet, par exemple hasAuthority('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 @EnableMethodSecurity active la sécurité au niveau des méthodes.
  • @PreAuthorize est une annotation puissante pour définir des règles d'accès directement sur vos méthodes de contrôleur, en utilisant des expressions comme hasRole('...') ou hasAuthority('...').
  • 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).