CodeGym /Cours /Spring Security /SecurityFilterChain : le squelette

SecurityFilterChain : le squelette

Spring Security
Niveau 5, Leçon 0
Disponible

1. Un SecurityFilterChain explicite

Il est temps de passer de l'observation à la maîtrise. Au lieu de vivre selon le principe « ce que Spring Security a décidé », on va commencer à décrire explicitement les règles d'accès en un seul endroit — dans son propre SecurityFilterChain. Et on va commencer le plus proprement possible : d'abord le squelette, sans essayer de tout configurer en une seule soirée (comme ça arrive souvent le vendredi).

«Par défaut» n'est pas une stratégie

Franchement, « le comportement par défaut » c'est comme vivre dans un appartement où le voisin a la télécommande du digicode et tu espères juste qu'il est quelqu'un de correct. Tant que tu as juste besoin que « ça marche », ça peut aller. Mais dès que ton projet a sa propre carte d'accès — articles publics, zone personnelle, zone rédacteur, zone admin — se reposer sur les défauts devient dangereux : tu ne contrôles pas ce qui « s'est ouvert », ce qui « s'est fermé » et pourquoi.

Les défauts de Spring Security posent aussi un autre problème : ils semblent pratiques exactement jusqu'au moment où tu essaies d'expliquer à un collègue (ou à toi-même dans deux semaines) pourquoi un endpoint précis répond 401, 403 ou redirige carrément vers une page HTML de login. Et là surgit quelque chose d'ennuyeux : quand la configuration est implicite, tu n'as pas « une seule page de vérité » où pointer du doigt et dire : « Regarde, voilà les règles ».

Disons-le clairement : dans une vraie application backend, la sécurité n'arrive pas « d'elle-même ». Elle est soit conçue et exprimée dans le code, soit c'est « un ensemble de hasards qui n'a pas encore planté ». Notre objectif — faire passer le projet de la seconde catégorie à la première, sans transformer Spring Security en religion et sans sacrifier la lisibilité.

Pour que cette idée s'ancre bien, il est utile de comparer deux approches — ce que font habituellement les débutants et ce qu'on fera dans ce cours :

Approche Où vivent les règles d'accès Ce qui se passe généralement avec le temps
«Je vérifie dans le contrôleur» if (user == null) throw ... dans @RestController Les règles se dispersent, se dupliquent, impossible de les voir en entier, des trous apparaissent
«Que Spring Security gère ça» Dans l'auto-config et les défauts On ne comprend pas pourquoi ça marche comme ça, difficile à déboguer, difficile à adapter au projet
SecurityFilterChain explicite En un seul endroit, dans la configuration security Les règles se lisent de haut en bas, plus facile à vérifier et modifier, moins de surprises

On suit justement la troisième voie : ni « à la main dans les contrôleurs » ni « on espère les défauts », mais une configuration explicite comme un artéfact ingénieur normal.

2. SecurityFilterChain dans la tête et dans Spring

Le mot SecurityFilterChain sonne comme quelque chose tiré d'un roman de conspiration, mais en pratique c'est plus simple. Le modèle mental est le suivant : SecurityFilterChain c'est un ensemble de règles et de filtres par lesquels passe une requête avant d'atteindre tes contrôleurs. Si la chaîne décide que la requête ne peut pas avancer, le contrôleur ne saura même pas que quelqu'un avait frappé à la porte.

Il est important de ne pas confondre deux niveaux. Au troisième jour, on a vu qu'il existe une infrastructure DelegatingFilterProxy → FilterChainProxy → SecurityFilterChain. Aujourd'hui on regarde de l'autre côté : SecurityFilterChain n'est pas juste « quelque chose à l'intérieur de Spring Security », c'est ce qu'on peut déclarer comme bean pour dire : « Voilà les règles de notre application, pas un abstract default ».

Pour solidifier, voilà un petit schéma (très simplifié, mais utile) :

flowchart TD
    %% Parcours simplifié d'une requête : du client via les filtres jusqu'aux contrôleurs
    C[Client] -->|HTTP| F[Servlet filters]
    %% Pont entre Servlet API et Spring Security
    F --> D[DelegatingFilterProxy]
    D --> P[FilterChainProxy]
    %% C'est ici que se branche notre chaîne (bean), pas la "magie par défaut"
    P --> S["SecurityFilterChain (notre bean)"]
    S --> M[DispatcherServlet → Controllers]

Autrement dit, la chaîne est littéralement le « tourniquet » à l'entrée. Tant que ce tourniquet n'a pas de règles claires — ou qu'elles sont cachées dans l'auto-config — tu peux écrire autant de belle logique métier dans les services : la requête soit n'y arrivera pas, soit y arrivera pas du tout là où tu l'attendais.

Autre pensée particulièrement utile pour les débutants : SecurityFilterChain n'est pas là pour "vérifier le mot de passe". La mécanique de vérification de l'utilisateur (authentication) est une histoire à part, et on a déjà rencontré ses acteurs principaux (AuthenticationManager, providers, etc.). Aujourd'hui on met l'accent sur autre chose : quelles requêtes sont accessibles à qui, c'est-à-dire les règles d'accès de base au niveau des URL. On ne vérifie pas le mot de passe à la main dans le contrôleur (et oui, c'est une bonne nouvelle).

3. HttpSecurity comme constructeur de règles

Voyons maintenant comment ça se présente dans le code. Dans Spring Security, on ne crée généralement pas SecurityFilterChain « à la main » depuis zéro. À la place, Spring nous fournit un objet HttpSecurity — qu'on peut commodément percevoir comme un builder, c'est-à-dire un constructeur de configuration pour une chaîne de sécurité.

HttpSecurity est important pour deux raisons. D'abord, c'est via lui qu'on décrit ce que doit faire notre sécurité : règles d'accès, gestion des erreurs, mécanismes de connexion et autres éléments. Ensuite, HttpSecurity est un « brouillon » qui ne se transforme en chaîne prête qu'au moment de l'appel à build(). C'est pourquoi le second terme clé du jour est http.build().

En langage humain, le processus ressemble à ça : « On a assemblé les paramètres, et maintenant on les "cuit" en un objet SecurityFilterChain prêt ». Oui, c'est presque comme de la pâtisserie : avant build() tu as la pâte, après — le pain. Si tu oublies build(), le pain, hélas, n'apparaîtra pas, et l'application ne démarrera pas ou se comportera de façon inattendue.

À ce stade, il est utile de rester concentré : aujourd'hui on n'essaie pas de tout configurer. On a besoin de deux choses : comprendre que HttpSecurity est le point d'assemblage, et voir le squelette minimal de SecurityFilterChain qui servira de « maison » pour toutes les règles suivantes de cette journée.

4. Le squelette dans le code : @Bean SecurityFilterChain

C'est le moment d'écrire ce squelette. Et ce qui est agréable ici, c'est que le code est vraiment court. Il nous faut une classe de configuration et une méthode qui retourne un SecurityFilterChain. Spring nous passera lui-même le HttpSecurity, et on retournera http.build().

On commence par la forme la plus « vide » possible pour voir la structure :

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration // Configuration Spring : d'ici sera capté le bean
public class SecurityConfig {

    @Bean // On rend SecurityFilterChain un bean managé, et non un "défaut de l'auto-config"
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Pour l'instant c'est un "squelette vide" : on ne configure rien, on assemble juste la chaîne
        return http.build();
    }
}

Si tu regardes ça et tu te demandes : « C'est tout ? », la réponse est oui — en terme de forme, c'est tout. Mais le sens est différent : à partir de ce moment la sécurité de l'application devient ton code, et non uniquement l'auto-config du starter. Tu disposes d'un point de contrôle dans lequel viendront s'ajouter les règles : ce qui est public, ce qui nécessite une connexion, ce qui est réservé à l'éditeur ou à l'administrateur.

C'est précisément ici que se produit un tournant important par rapport au starter default. Tant que tu n'avais pas de SecurityFilterChain à toi, la politique d'accès au niveau des requêtes était définie par la configuration standard de Spring Security. Dès que la chaîne est déclarée comme bean, les règles « qui va où » se lisent depuis ton code. Les béquilles pédagogiques comme form login, HTTP Basic ou generated user peuvent rester en place pour l'instant — mais elles fonctionnent maintenant dans la politique qu'on a assemblée explicitement.

Mais il y a une nuance pratique ici. Un pur http.build(), c'est seulement le squelette. Dans une vraie application, pour que le comportement soit prévisible et ne « flotte » pas d'une version à l'autre, on fixe habituellement au moins une baseline minimale. Dans le cadre du cours, il nous convient de garder l'idée du secure-by-default : d'abord tout est fermé, ensuite on ouvre ponctuellement les zones publiques.

Ci-dessous — la baseline opérationnelle qu'il est commode de suivre ensuite : le squelette de la chaîne et les mécanismes de connexion de base restent en place, et l'essentiel du travail se fera dans les règles d'accès elles-mêmes.

Voilà donc la « baseline de départ sécurisée » : l'application reste fermée, mais on dispose déjà d'un mécanisme d'authentification pour pouvoir vérifier les accès :

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                // Règle de base : toute requête nécessite une authentification
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                // On active le form login "par défaut" pour pouvoir tester via le navigateur
                .formLogin(Customizer.withDefaults())
                // On active HTTP Basic "par défaut" pour pouvoir vérifier facilement via Postman/curl
                .httpBasic(Customizer.withDefaults())
                // On finalise le builder : sans build() la chaîne ne sera pas créée
                .build();
    }
}

Il est important de bien lire ce qui se passe ici, mais sans théorie prématurée. On n'étudie pas authorizeHttpRequests et on ne détaille pas ce qui distingue authenticated() de hasRole() — ce sont les sujets des prochaines lectures de la journée. Pour l'instant, on retient uniquement le sens : on a un endroit où vivront les règles, et une possibilité minimale de se connecter pour vérifier les accès à la main.

Si formLogin(...) et httpBasic(...) t'inquiètent, bonne nouvelle : on ne va pas les personnaliser et on n'en fait pas un sujet à part. On laisse simplement activés les mécanismes de base, généralement utiles au démarrage pour tester les accès aussi bien depuis le navigateur que depuis Postman. On parlera de form login et HTTP Basic en détail en temps voulu — pour l'instant, on n'y plonge pas en profondeur.

Et encore une chose importante : dans Spring Boot, tu n'as généralement pas besoin d'enregistrer les filtres manuellement. Il suffit que SecurityFilterChain soit déclaré comme @Bean. Boot et Spring Security le captent, et FilterChainProxy commence à utiliser exactement cette chaîne.

5. Emplacement de la configuration et une seule chaîne

Quand un débutant ajoute pour la première fois une configuration de sécurité, il a souvent envie de la mettre n'importe où : dans config, dans common, ou « mettons directement dans la classe main pour ne pas chercher ». Mais la sécurité est la couche qu'on finit à lire plus souvent qu'on ne le voudrait. Mieux vaut donc lui donner une adresse claire dès le début.

Dans notre projet fil rouge Secure Content Platform API, on garde le code lié à la sécurité dans une zone à part, par exemple ainsi :

src/main/java/com/example/securecontent/security/config/SecurityConfig.java

Pourquoi c'est plus pratique ? D'abord, tout ce qui concerne la sécurité est au même endroit et ne se mélange pas avec les fonctionnalités métier content, profile, admin. Ensuite, quand tu cherches pourquoi /api/admin/users est soudainement devenu public, tu n'épluches pas vingt contrôleurs — tu ouvres une seule config.

Et mettons-nous d'accord sur une limite importante de cette journée : aujourd'hui on fait une seule chaîne. Spring Security sait travailler avec plusieurs SecurityFilterChain, et parfois c'est puissant et utile, mais dans un cours fondamental ça se transforme facilement en « pourquoi ma requête est allée dans l'autre chain ? ». Donc dans le cadre du Checkpoint 1, l'objectif est plus simple : une chaîne, une liste de règles lisible, de haut en bas.

6. Erreurs typiques avec SecurityFilterChain

Erreur n°1 : oublier @Bean ou @Configuration, et tu as « configuré la security » mais Spring ne le sait pas.
Ça arrive tout le temps : la classe est belle, la méthode est correcte, mais l'annotation est oubliée — et l'application continue à vivre sur l'auto-config. Remède simple : vérifie que la méthode est annotée @Bean et la classe @Configuration (ou qu'elle est bien importée et scannée par Spring).

Erreur n°2 : on a retourné un SecurityFilterChain, mais sans appeler build().
HttpSecurity n'est pas un SecurityFilterChain. C'est un builder. Si tu retournes quelque chose d'autre ou tu oublies de finaliser la configuration, tu laisses en quelque sorte un objet « à moitié cuit ». La fin doit être claire et unique : return http.build() ou une chaîne return http....build().

Erreur n°3 : essayer de « tout configurer d'un coup », comme si c'était un concours du DSL le plus long.
Les débutants ont souvent une impulsion : « Puisque j'ai ouvert HttpSecurity, j'ajoute tout de suite CORS, CSRF, session policy, handlers, filtres custom, gestion des exceptions… ». Résultat — une config de 200 lignes que personne ne comprend. Dans ce module, notre objectif est différent : une configuration courte et lisible. On ajoutera tout le reste progressivement, quand le contexte sera là.

Erreur n°4 : transférer la logique security dans les contrôleurs « juste parce que c'est plus rapide pour l'instant ».
Si tu as déjà ajouté un SecurityFilterChain mais que tu continues à écrire dans les contrôleurs if (!isAdmin) return 403, tu obtiens un double système de sécurité qui finira par se contredire. Les règles URL doivent vivre dans la configuration, pas être dispersées entre les endpoints. Sinon Spring Security devient une décoration, pas une couche architecturale.

Erreur n°5 : essayer de « tout désactiver ce qui dérange » pour que ça « marche juste ».
Aux premiers pas, la tentation est grande d'écrire quelque chose comme « je coupe la protection, je la remettrai plus tard ». Le problème, c'est que ce « plus tard » n'arrive généralement pas, et l'habitude s'installe. Dans ce cours, on garde à l'esprit le secure-by-default : d'abord tout est fermé, ensuite on ouvre de façon réfléchie les zones nécessaires. C'est un peu moins excitant, mais beaucoup plus sûr et prévisible.

1
Mission
Spring Security, niveau 5, leçon 0
Bloqué
Un seul `SecurityFilterChain` explicite
Un seul `SecurityFilterChain` explicite
1
Mission
Spring Security, niveau 5, leçon 0
Bloqué
Nom du bean `securityFilterChain` dans le contexte
Nom du bean `securityFilterChain` dans le contexte
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION