
Architecture de Spring Security (filtres, contexte de sécurité)
Comprenez le fonctionnement interne de Spring Security : la chaîne de filtres Servlet (FilterChainProxy), les filtres clés et le rôle central du SecurityContextHolder pour gérer l'état d'authentification.
Les filtres Servlet : la fondation de Spring Security
Au coeur de l'architecture de Spring Security se trouve son intégration profonde avec l'API Servlet standard, et plus particulièrement le mécanisme des Filtres Servlet (`javax.servlet.Filter` ou `jakarta.servlet.Filter` selon la version). Les filtres sont des composants Java qui peuvent intercepter les requêtes entrantes avant qu'elles n'atteignent le servlet (ou le contrôleur Spring MVC) et les réponses sortantes avant qu'elles ne soient renvoyées au client.
Cette capacité d'interception permet aux filtres d'effectuer des traitements transversaux, tels que la journalisation, la compression, la transformation des requêtes/réponses, et surtout, la sécurité. Spring Security exploite cette architecture en insérant une chaîne de filtres spécialisés dans le pipeline de traitement des requêtes.
Plutôt que d'enregistrer de nombreux filtres individuellement dans le `web.xml` ou via la configuration Servlet, Spring Security utilise une approche plus élégante et centralisée grâce à un filtre principal unique : le `FilterChainProxy`.
Le `FilterChainProxy` : le portier unique
Le `FilterChainProxy` est un filtre spécial fourni par Spring Security qui est généralement enregistré comme un bean nommé `springSecurityFilterChain` dans le contexte d'application Spring. C'est le point d'entrée unique pour toutes les fonctionnalités de sécurité de Spring Security au niveau des requêtes web. Il agit comme un **délégateur**.
Sa responsabilité principale n'est pas d'implémenter la logique de sécurité elle-même, mais de déléguer la requête à une chaîne spécifique de filtres de sécurité internes (une `SecurityFilterChain`). L'avantage de cette approche est multiple :
- Centralisation : Toute la configuration de la sécurité web passe par ce point unique.
- Flexibilité : Il peut gérer plusieurs chaînes de filtres et sélectionner la chaîne appropriée en fonction de l'URL de la requête (par exemple, une chaîne pour `/api/**` et une autre pour `/admin/**`).
- Gestion par Spring : Les filtres internes de la chaîne sont des beans Spring, bénéficiant de l'injection de dépendances et de la gestion du cycle de vie par le conteneur.
- Ordre contrôlé : L'ordre d'exécution des filtres de sécurité est explicitement défini dans la configuration de la `SecurityFilterChain`.
En résumé, le `FilterChainProxy` est le pont entre le monde des Servlets standard et l'infrastructure de sécurité gérée par Spring.
La `SecurityFilterChain` : l'orchestre des filtres de sécurité
Une `SecurityFilterChain` est une liste ordonnée de filtres de sécurité gérés par Spring qui traitent une requête spécifique. Chaque filtre a une responsabilité bien définie dans le processus global de sécurisation.
Voici quelques exemples de filtres couramment trouvés dans une chaîne de sécurité Spring Security :
SecurityContextPersistenceFilter(ou ses équivalents modernes) : Charge leSecurityContext(contenant l'état d'authentification) depuis la session HTTP (ou une autre source) au début de la requête et le sauvegarde à la fin.UsernamePasswordAuthenticationFilter: Intercepte les requêtes de connexion (typiquementPOST /login) et tente d'authentifier l'utilisateur à partir des identifiants fournis.BasicAuthenticationFilter: Gère l'authentification HTTP Basic.BearerTokenAuthenticationFilter(pour OAuth2/JWT) : Extrait et valide un Bearer Token (souvent un JWT) depuis l'en-têteAuthorization.CsrfFilter: Applique la protection contre les attaques CSRF en vérifiant la présence et la validité d'un token CSRF.FilterSecurityInterceptor: C'est généralement le dernier filtre important. Il est responsable de l'autorisation. Il intercepte l'appel à la ressource protégée et vérifie si l'utilisateur authentifié (présent dans leSecurityContext) a les permissions nécessaires (rôles, autorités) pour y accéder, en se basant sur les règles configurées (par exemple viahttp.authorizeHttpRequests()).
L'ordre de ces filtres est crucial. Par exemple, l'authentification doit généralement avoir lieu avant l'autorisation. Spring Security définit un ordre par défaut pour les filtres standards, mais vous pouvez insérer des filtres personnalisés à des positions spécifiques dans la chaîne.
Le `SecurityContextHolder` et le `SecurityContext` : le coeur de l'état de sécurité
Comment l'information sur l'utilisateur authentifié est-elle conservée et partagée entre les différents filtres et le reste de l'application pendant le traitement d'une requête ? C'est le rôle du `SecurityContextHolder` et du `SecurityContext`.
Le `SecurityContextHolder` est une classe utilitaire qui fournit un accès statique au contexte de sécurité. Par défaut, il utilise une stratégie basée sur `ThreadLocal` pour stocker le contexte, ce qui signifie que le `SecurityContext` est lié au thread courant qui traite la requête. Cela garantit que l'état de sécurité est isolé entre les différentes requêtes traitées simultanément.
Le `SecurityContext` est l'interface qui contient l'objet `Authentication`. C'est cet objet `Authentication` qui représente réellement l'état d'authentification de l'utilisateur pour la requête en cours. S'il n'y a pas d'utilisateur authentifié (requête anonyme), l'objet `Authentication` dans le `SecurityContext` sera généralement `null` ou un objet représentant un utilisateur anonyme.
L'objet `Authentication` contient typiquement trois informations clés :
- Principal : L'identité de l'utilisateur. Cela peut être un objet `UserDetails` (interface standard de Spring Security), un objet métier spécifique, ou simplement un nom d'utilisateur (String).
- Credentials : Les informations utilisées pour prouver l'identité (par exemple, le mot de passe). Celles-ci sont souvent effacées après une authentification réussie pour des raisons de sécurité.
- Authorities : Une collection de `GrantedAuthority` représentant les permissions ou les rôles accordés à l'utilisateur (par exemple, `ROLE_ADMIN`, `READ_PRIVILEGE`). C'est ce que les mécanismes d'autorisation utilisent pour prendre des décisions.
Flux d'une requête à travers l'architecture
Pour illustrer comment ces composants interagissent, considérons le flux typique d'une requête vers une ressource protégée :
- La requête arrive au serveur et est interceptée par le `FilterChainProxy`.
- Le `FilterChainProxy` sélectionne la `SecurityFilterChain` appropriée pour l'URL demandée.
- La requête passe à travers les filtres de la chaîne dans l'ordre défini.
- Un filtre comme `SecurityContextPersistenceFilter` tente de charger le `SecurityContext` (par exemple, depuis la session HTTP). S'il trouve un `Authentication` valide, il le place dans le `SecurityContextHolder`.
- Si aucun `Authentication` valide n'est trouvé et que la ressource nécessite une authentification, un filtre d'authentification (comme `UsernamePasswordAuthenticationFilter` ou `BearerTokenAuthenticationFilter`) peut tenter d'authentifier l'utilisateur basé sur les informations de la requête (identifiants de formulaire, en-tête `Authorization`).
- Si l'authentification réussit, le filtre d'authentification crée un objet `Authentication` complet (avec principal et autorités) et le place dans le `SecurityContext` via le `SecurityContextHolder`.
- La requête continue sa progression dans la chaîne.
- Finalement, le `FilterSecurityInterceptor` intervient. Il récupère l'`Authentication` depuis le `SecurityContextHolder` et vérifie si les `authorities` de l'utilisateur l'autorisent à accéder à la ressource demandée, selon les règles de sécurité configurées.
- Si l'autorisation est accordée, la requête atteint enfin le contrôleur Spring MVC ou le servlet cible. Sinon, une exception d'accès refusé (`AccessDeniedException`) est levée.
- Sur le chemin du retour, les filtres peuvent effectuer des actions sur la réponse (par exemple, ajouter des en-têtes de sécurité). Le `SecurityContextPersistenceFilter` sauvegarde généralement le `SecurityContext` (par exemple, dans la session) avant que la réponse ne soit envoyée au client.
- Le `SecurityContextHolder` est nettoyé (le `SecurityContext` est retiré du `ThreadLocal`) pour éviter les fuites d'informations entre les requêtes.
Comprendre cette architecture basée sur les filtres et le rôle central du `SecurityContextHolder` est fondamental pour configurer et dépanner efficacement Spring Security dans vos applications Spring Boot.