
Autorisation basée sur les rôles (`hasRole`, `hasAnyRole`)
Maîtrisez l'autorisation basée sur les rôles dans Spring Security en utilisant les expressions SpEL hasRole et hasAnyRole pour contrôler l'accès aux ressources.
Introduction à l'autorisation basée sur les rôles (RBAC)
Une fois qu'un utilisateur est authentifié (c'est-à-dire que son identité a été vérifiée), l'étape suivante consiste à déterminer ce qu'il est autorisé à faire dans l'application. C'est le domaine de l'autorisation. L'une des approches les plus courantes et intuitives pour gérer les permissions est le Contrôle d'Accès Basé sur les Rôles (RBAC - Role-Based Access Control).
Dans un modèle RBAC, les permissions ne sont pas attribuées directement aux utilisateurs individuels, mais plutôt à des rôles. Les utilisateurs se voient ensuite attribuer un ou plusieurs rôles. Lorsqu'un utilisateur tente d'accéder à une ressource ou d'effectuer une action, le système vérifie si l'un des rôles attribués à cet utilisateur possède la permission requise.
Spring Security offre un support robuste pour le RBAC, notamment via des expressions SpEL (Spring Expression Language) intégrées comme `hasRole()` et `hasAnyRole()`, qui peuvent être utilisées directement dans la configuration de la sécurité web (`HttpSecurity`) ou dans les annotations de sécurité au niveau des méthodes.
Rôles et autorités dans Spring Security : la convention `ROLE_`
Dans Spring Security, le concept de permission est généralement représenté par l'interface `GrantedAuthority`. Chaque instance de `GrantedAuthority` représente une autorisation individuelle accordée à l'utilisateur authentifié (l'`Authentication` obtenue via `SecurityContextHolder`).
Bien que vous puissiez utiliser n'importe quelle chaîne de caractères comme nom d'autorité (par exemple, `permission_lecture_article`), Spring Security établit une convention forte lorsqu'il s'agit de "rôles". Un rôle est considéré comme une `GrantedAuthority` dont le nom commence par le préfixe `ROLE_` (par exemple, `ROLE_ADMIN`, `ROLE_USER`, `ROLE_MODERATOR`).
Cette convention est importante car les expressions SpEL comme `hasRole()` et `hasAnyRole()` s'appuient dessus. Elles ajoutent automatiquement ce préfixe `ROLE_` lors de la vérification des autorités de l'utilisateur. Si vos autorités de rôle ne suivent pas cette convention, vous devrez utiliser les expressions basées sur les autorités (`hasAuthority()`, `hasAnyAuthority()`) à la place.
L'expression `hasRole('NOM_DU_ROLE')`
L'expression `hasRole('X')` est utilisée pour vérifier si l'utilisateur actuellement authentifié possède le rôle spécifié 'X'. Conformément à la convention, Spring Security vérifiera en réalité si l'utilisateur possède une `GrantedAuthority` dont le nom est `ROLE_X`.
Par exemple, pour restreindre l'accès à certaines URL uniquement aux utilisateurs ayant le rôle d'administrateur, vous utiliseriez `hasRole('ADMIN')` dans votre configuration `HttpSecurity` :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
// Seuls les utilisateurs avec l'autorité "ROLE_ADMIN" peuvent accéder
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/gestion/utilisateurs").hasRole("ADMIN")
// ... autres règles ...
.anyRequest().authenticated()
)
// ... configuration formLogin, etc. ...
;
return http.build();
}Dans cet exemple, si un utilisateur tente d'accéder à `/admin/console` ou `/gestion/utilisateurs`, Spring Security récupérera l'`Authentication` de l'utilisateur depuis le `SecurityContextHolder` et vérifiera si sa collection d'`authorities` contient une `GrantedAuthority` égale à `"ROLE_ADMIN"`. Si ce n'est pas le cas, une exception `AccessDeniedException` sera levée (conduisant généralement à une page d'erreur 403 Forbidden).
Il est crucial que votre `UserDetailsService` (ou le mécanisme équivalent) charge correctement les autorités de l'utilisateur en respectant ce format `ROLE_NOM_DU_ROLE` pour que `hasRole` fonctionne comme prévu.
L'expression `hasAnyRole('ROLE1', 'ROLE2', ...)`
L'expression `hasAnyRole('X', 'Y', 'Z')` est utilisée pour vérifier si l'utilisateur actuellement authentifié possède au moins un des rôles spécifiés dans la liste ('X', 'Y', ou 'Z'). Tout comme `hasRole`, elle ajoute automatiquement le préfixe `ROLE_` à chaque nom de rôle fourni lors de la vérification.
C'est une manière concise de définir des accès pour des utilisateurs ayant différents rôles mais partageant certaines permissions. Par exemple, si les administrateurs et les modérateurs peuvent tous deux accéder à un panneau de modération :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
// Les utilisateurs avec ROLE_ADMIN ou ROLE_MODERATOR peuvent accéder
.requestMatchers("/moderation/**").hasAnyRole("ADMIN", "MODERATOR")
// ... autres règles ...
.anyRequest().authenticated()
)
// ... configuration formLogin, etc. ...
;
return http.build();
}Dans ce cas, un utilisateur accédant à `/moderation/commentaires` sera autorisé s'il possède l'autorité `ROLE_ADMIN` ou l'autorité `ROLE_MODERATOR`. Si l'utilisateur ne possède aucun de ces deux rôles, l'accès sera refusé.
Cette expression est plus pratique que d'écrire des conditions OR complexes avec plusieurs `hasRole` (bien que cela soit possible avec SpEL : `hasRole('ADMIN') or hasRole('MODERATOR')`).
Chargement des rôles via `UserDetailsService`
Pour que les vérifications `hasRole` et `hasAnyRole` fonctionnent, les autorités correspondantes doivent être correctement chargées et associées à l'objet `Authentication` de l'utilisateur lors du processus d'authentification. C'est typiquement la responsabilité de votre implémentation de `UserDetailsService`.
Lorsque vous implémentez la méthode `loadUserByUsername`, vous devez récupérer les rôles de l'utilisateur depuis votre source de données et les transformer en une collection de `GrantedAuthority`, en veillant à inclure le préfixe `ROLE_`.
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
// ... injection du repository ...
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = // ... charger l'utilisateur depuis la BD ...
.orElseThrow(() -> new UsernameNotFoundException(...));
// Supposons que userEntity.getRoles() retourne un Set comme {"ADMIN", "USER"}
Collection extends GrantedAuthority> authorities =
userEntity.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role)) // Ajout du préfixe ici !
.collect(Collectors.toList());
return new User(
userEntity.getUsername(),
userEntity.getPassword(),
authorities // Les autorités avec le préfixe ROLE_
);
}
} Si le préfixe `ROLE_` n'est pas ajouté lors de la création des `GrantedAuthority`, les vérifications `hasRole('ADMIN')` échoueront car elles chercheront `ROLE_ADMIN` alors que l'autorité réelle pourrait être simplement `ADMIN`.
Au-delà de `hasRole` : `hasAuthority`
Il est important de noter que si votre modèle de permissions n'utilise pas la convention de préfixe `ROLE_`, ou si vous souhaitez vérifier des permissions plus granulaires qui ne sont pas conceptuellement des "rôles" (par exemple, `permission_lire_rapport`, `action_supprimer_commentaire`), vous devriez utiliser les expressions `hasAuthority('NOM_AUTORITE')` et `hasAnyAuthority('AUTORITE1', 'AUTORITE2')`.
Ces expressions fonctionnent de manière similaire à `hasRole` et `hasAnyRole`, mais elles effectuent une comparaison exacte de la chaîne de caractères fournie avec les `GrantedAuthority` de l'utilisateur, sans ajouter automatiquement de préfixe.
http
.authorizeHttpRequests(authorize -> authorize
// Vérifie l'autorité exacte "permission_rapport_ventes"
.requestMatchers("/rapports/ventes").hasAuthority("permission_rapport_ventes")
.requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN") // Fonctionne aussi si le préfixe est déjà dans l'autorité
.anyRequest().authenticated()
);
Le choix entre les expressions basées sur les rôles et celles basées sur les autorités dépend de la manière dont vous modélisez vos permissions et si vous adhérez à la convention `ROLE_` de Spring Security.