CodeGym /Cours /Spring Security /Utilisateur anonymous et SecurityContext

Utilisateur anonymous et SecurityContext

Spring Security
Niveau 3, Leçon 3
Disponible

1. Le rôle de l'utilisateur anonymous : un modèle sans vide

Spring Security s'efforce que chaque requête puisse être décrite de façon uniforme. On voudrait penser simplement : « si l'utilisateur est connecté — il existe, s'il ne l'est pas — il n'existe pas ». En programmation, cette simplicité se termine généralement avec la moitié du code transformée en vérifications de null, et l'autre moitié en bugs inattendus, parce qu'on a oublié une vérification quelque part. Spring Security résout ce problème non pas par la philosophie, mais par l'ingénierie.

Imagine notre projet Secure Content Platform API. Par conception du domaine, on a des articles publics (/api/public/articles) et une zone personnelle (/api/me, brouillons, profil). Même si tu n'as encore écrit aucun paramètre de sécurité, tu as déjà vu au deuxième jour que Spring Security sait « couper » les requêtes avant le contrôleur. Et c'est là qu'apparaît une idée importante : la couche security doit pouvoir décrire de façon également correcte aussi bien une requête d'un utilisateur connecté qu'une requête d'un « visiteur ».

Si le « visiteur » n'est que l'absence de quelque chose, dans le style « il n'y a rien dans le contexte », il est très difficile de construire ensuite un système de règles. Mais si le « visiteur » est un état technique clair, les règles deviennent plus simples : « toutes les requêtes passent par le même mécanisme, juste avec des données différentes dans le contexte ».

Dans Spring Security, cet état s'appelle anonymous authentication : à la place de l'idée « il n'y a rien » apparaît l'idée « il y a un utilisateur anonymous ».

2. SecurityContext : l'état de la requête

Dans cette section, l'important n'est pas d'essayer de mémoriser toutes les classes immédiatement. Mieux vaut imaginer une chose simple : pendant le traitement d'une requête HTTP, l'application a besoin d'un endroit où est stockée la réponse à la question « qui fait la requête en ce moment ». C'est exactement cet endroit qu'est le SecurityContext.

Le SecurityContext est un petit objet-conteneur qui stocke (en premier lieu) un objet Authentication. On peut le concevoir comme un badge à une conférence. Tant que tu parcours le couloir — les filtres et MVC — le badge est avec toi. Avec le badge, on peut comprendre : tu es visiteur, participant, organisateur, conférencier — c'est-à-dire quel est ton statut. Et ce qui est important : même si tu es « juste venu regarder », le badge est quand même utile — la sécurité n'a pas à deviner qui tu es.

Du point de vue de Spring Security, le SecurityContext est créé et préparé à l'intérieur de la security filter chain aux premières étapes, pour que le reste de la chaîne puisse s'appuyer dessus. Ça rend le modèle stable : les filtres suivants, MVC et ton code n'ont pas à inventer « encore une autre façon de comprendre qui est l'utilisateur ».

En simplifiant à une formule, pour chaque requête l'idée est la suivante :

Il y a une requête → il y a un SecurityContext → il contient une Authentication (ou au moins un remplaçant clair)

Et ça nous amène à la question : « Où se trouve ce contexte et comment y accéder ? »

3. SecurityContextHolder : accès au contexte

À première vue, le SecurityContext ressemble à une « variable globale de l'utilisateur courant ». Mais Spring Security fait ça proprement : il ne te propose pas de stocker l'utilisateur dans un champ statique quelconque — ce serait un billet pour un monde de douleur. À la place, il y a un mécanisme d'accès officiel : le SecurityContextHolder.

Le SecurityContextHolder est le point d'accès au contexte de sécurité pendant le traitement de la requête courante. Le mot clé ici est « courante ». Le contexte ne doit pas fuiter entre requêtes, utilisateurs et threads. Spring Security organise le traitement de façon à ce que, dans le cadre d'une requête, tu aies un contexte correct, et qu'après la fin de la requête il ne reste pas suspendu comme le fantôme d'un ancien utilisateur.

Un exemple minimal «pour tâter de la main» ressemble à ceci (à l'intérieur de ton application) :

import org.springframework.security.core.context.SecurityContextHolder;

public class Demo {

    public void printContext() {
        // On récupère le contexte de sécurité de la requête/thread courant
        var context = SecurityContextHolder.getContext();

        // L'affichage est pratique pour le diagnostic : dans la vraie logique, on ne fait pas ça habituellement
        System.out.println(context); // Affiche l'objet contexte (le contenu dépend de la requête)
    }
}

L'important ici est de saisir l'idée : tu ne crées pas le SecurityContext à la main dans une application ordinaire. C'est la chaîne security qui le prépare. Tu peux seulement, en un point correct — par exemple dans le contrôleur — regarder ce qui s'y trouve.

Et maintenant le principal retournement de cette leçon : même si l'utilisateur n'est pas connecté, le contexte peut quand même avoir du sens.

4. Anonymous authentication : un état à la place de null

C'est là que les débutants ressentent souvent une dissonance cognitive : « Attendez, vous voulez dire que l'"utilisateur anonymous" est aussi une Authentication ? ». Oui. C'est exactement ce que Spring Security fait par défaut.

Spring Security a une configuration anonymous authentication dédiée, dont le sens est formulé très directement : placer dans le contexte une Authentication représentant un utilisateur anonymous, à la place de null. Dans une configuration typique, c'est fait via la liaison AnonymousAuthenticationFilter et AnonymousAuthenticationProvider, activée par défaut avec des valeurs raisonnables.

Pourquoi c'est pratique ? Parce qu'ensuite tout le système fonctionne sans « trous » dans le modèle :

  • les filtres et le code d'autorisation peuvent s'appuyer sur le fait que l'Authentication est soit présente (et on peut alors regarder les authorities/le rôle), soit vraiment absente dans des scénarios particuliers (et c'est déjà un signal d'une situation inhabituelle) ;
  • on peut décider uniformément « laisser passer » ou « arrêter » une requête ;
  • on peut séparer les scénarios « visiteur » et « utilisateur » sans transformer ça en chaos de if.

Pour rendre l'anonymat non pas un « concept en mots » mais un état vérifiable, Spring Security utilise un type d'Authentication spécial : l'AnonymousAuthenticationToken. Et la plupart du temps, il porte l'idée suivante : principal = anonymousUser et quelques authorities minimales comme ROLE_ANONYMOUS.

On n'a pas besoin de mémoriser quelles chaînes exactes s'y trouvent. L'important est autre chose : l'anonymat n'est pas une absence d'état, c'est un état distinct.

Un petit détail important sur Principal dans Spring MVC

Intuitivement, on voudrait vérifier l'anonymat ainsi : « si le Principal dans le contrôleur est null — c'est un visiteur ». Souvent, ça fonctionne. Mais Spring Security a une nuance ici : la façon dont Spring MVC remplit les paramètres du contrôleur (Principal, Authentication) s'appuie sur HttpServletRequest#getPrincipal, et pour les requêtes anonymes il peut être null. Spring Security en avertit officiellement et propose d'obtenir le contexte autrement, si tu as vraiment besoin de voir l'anonymat précisément comme une Authentication.

On ne va pas pour l'instant dans les annotations avancées, mais la conclusion est simple : Principal est pratique, tandis que SecurityContextHolder est plus fiable comme « source de vérité » si tu as besoin précisément de l'état security.

5. L'utilisateur courant dans le contrôleur : Principal

Voici la partie la plus pratique. Dans la plupart des projets pédagogiques et réels, tu voudras tôt ou tard un endpoint « montre-moi qui je suis ». Dans notre domaine, ça ressemble à /api/me, mais on fait pour l'instant un petit pas diagnostique pour voir le mécanisme lui-même, pas la logique métier du profil.

Dans Spring MVC, on peut demander au framework de transmettre l'utilisateur courant à une méthode de contrôleur via le paramètre Principal. Si la requête est anonyme, le Principal sera souvent null. Si l'utilisateur est connecté, le Principal aura un nom (généralement le username).

Créons un endpoint simple :

import java.security.Principal;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WhoAmIController {

    @GetMapping("/api/debug/whoami")
    public String whoAmI(Principal principal) {
        // Pour une requête anonyme, principal sera souvent null — c'est le comportement attendu
        return principal == null ? "guest" : principal.getName(); // Le nom correspond généralement au username
    }
}

Ce que tu obtiens en tant que développeur ? Une sémantique applicative très simple : soit guest, soit le nom de l'utilisateur. C'est utile pour cesser de penser « où Spring Security stocke-t-il l'utilisateur ? ». Il le stocke de façon à ce que MVC puisse, si besoin, te le transmettre dans le contrôleur.

Il est important de garder en tête la limite : cet endpoint ne doit pas devenir ton « API métier de profil ». Il est diagnostique. Il est là pour qu'en tant qu'ingénieur débutant, tu réalises clairement : l'utilisateur courant n'est pas une variable quelque part dans ton service, c'est une partie de l'infrastructure de la requête.

6. L'utilisateur courant via SecurityContextHolder

Parfois Principal est pratique, mais parfois tu veux voir exactement ce que voit la couche security : quelle Authentication se trouve actuellement dans le contexte. C'est particulièrement utile pour distinguer « le contrôleur n'a pas été appelé » de « le contrôleur a été appelé mais l'intérieur est vide » — et ne pas organiser une enquête style « Sherlock Holmes et l'endpoint disparu ».

Créons un deuxième endpoint qui récupère l'information directement depuis le SecurityContextHolder :

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WhoAmIFromContextController {

    @GetMapping("/api/debug/whoami-context")
    public String whoAmIFromContext() {
        // On récupère l'Authentication directement depuis le SecurityContext de la requête courante
        var auth = SecurityContextHolder.getContext().getAuthentication();

        // Normalement pour anonymous, il peut y avoir un AnonymousAuthenticationToken, pas null
        return auth == null ? "no-authentication" : auth.getName();
    }
}

Ce code est un peu moins « humain », mais plus direct. Il illustre l'idée principale de la leçon : l'« utilisateur courant » pour l'application, c'est ce qui se trouve dans le SecurityContext de la requête courante.

Et si tu veux non seulement le nom, mais aussi comprendre « cette requête est-elle anonyme ou non », on peut vérifier via AnonymousAuthenticationToken :

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

public class AuthFlags {

    public static boolean isAnonymous() {
        // On récupère l'Authentication courante depuis le contexte
        var auth = SecurityContextHolder.getContext().getAuthentication();

        // Important : "anonymous" est un type de token distinct, pas juste null
        return auth instanceof AnonymousAuthenticationToken;
    }
}

Remarque le style d'ingénierie : petite fonction, nom clair, minimum de détails. Cette approche évite de traîner des vérifications instanceof dans tout le projet et d'en faire du spaghetti de vérifications security.

Quand cette image se construit dans la tête, les logs security commencent à se lire plus facilement. Au lieu d'un vague « quelque chose a mal tourné », tu comprends déjà quelle Authentication tu attends dans le contexte et pourquoi anonymous est un état normal de la requête, pas une panne.

7. L'utilisateur security et le modèle du domaine

Cette section économise généralement aux étudiants quelques semaines de douleur (et un tic nerveux à l'œil). Dans Spring Security, l'« utilisateur » à l'intérieur de l'Authentication est une représentation security. Elle répond aux questions « qui est venu » et « quels sont ses droits/statuts », mais n'est pas obligée de contenir ton vrai profil, ta bio, ton avatar, ta liste de brouillons et ta couleur préférée pour le bouton « Publier ».

Pourquoi est-il important de comprendre ça maintenant, avant les modules suivants ? Parce que sinon, le débutant commence à faire des choses dangereuses. Par exemple, essayer de stocker « l'entité User complète depuis la base » directement dans le SecurityContext en permanence. Ou, encore plus drôle, y fourrer des choses qui n'ont rien à voir avec la sécurité (par exemple, des paramètres d'UI). Ça brise les limites de responsabilité.

À ce stade du cours, il suffit de retenir la règle : le SecurityContext et l'Authentication fournissent l'information minimalement suffisante pour prendre des décisions security au niveau de la requête. Et les données métier — c'est une couche séparée. C'est pourquoi, quand tu vois auth.getName(), perçois ça comme « identifiant de l'utilisateur dans le monde security », pas comme « tout ce que je sais sur la personne ».

Pour faire une analogie : l'Authentication est un badge d'accès au bureau, et UserProfile est le dossier personnel de l'employé. Le badge sert à passer le tourniquet, mais le tourniquet n'a pas besoin de connaître ta biographie.

8. Erreurs fréquentes avec l'anonymous authentication

Erreur n°1 : considérer que l'utilisateur anonymous c'est « absolument rien ».
Les débutants pensent souvent que, avant la connexion à l'application, il n'y a aucun utilisateur, et qu'on peut donc écrire partout « si l'utilisateur n'existe pas — on fait X ». Dans Spring Security, il vaut mieux penser autrement : la requête passe quand même par le modèle security, et anonymous est un état distinct et normal. Ainsi, tu évites le chaos des null et tu obtiens un style unifié de prise de décisions. Et Spring Security active justement l'anonymous authentication par défaut — pour qu'il y ait dans le contexte non pas null, mais une Authentication sensée d'un utilisateur anonymous.

Erreur n°2 : confondre Principal et Authentication comme « toujours la même chose ».
Principal dans le contrôleur est une façon applicative pratique d'obtenir le nom de l'utilisateur, mais il ne reflète pas toujours toute la réalité de la couche security. En particulier, dans les requêtes anonymes, HttpServletRequest#getPrincipal peut être null, et donc les tentatives de déterminer l'anonymat uniquement par les paramètres du contrôleur peuvent se comporter de façon inattendue. Si tu as besoin d'informations précises de la couche security, mieux vaut regarder dans le SecurityContext. Cette nuance est directement décrite dans la documentation Spring Security pour l'anonymous authentication et l'intégration MVC.

Erreur n°3 : stocker l'utilisateur courant dans ses propres variables statiques ou singletons « en mémoire ».
Parfois, un étudiant trouve logique de « lire l'utilisateur une fois depuis le contexte et le sauvegarder dans un champ de service ». Ça brise toute l'idée de l'état request-scoped : la prochaine requête peut venir d'un autre utilisateur, mais tu travailleras avec l'ancienne valeur. Le bon modèle : SecurityContextHolder est un accès au contexte pendant la durée de la requête courante, pas plus.

Erreur n°4 : essayer de mettre des objets métier dans le SecurityContext « pour que ce soit pratique ».
On a très envie d'y mettre tout le UserProfile, la liste des rôles, l'avatar et les articles favoris, pour ne pas avoir à aller chercher ailleurs. Mais le SecurityContext concerne la sécurité, pas le métier. À ce stade, il suffit de traiter l'Authentication comme une « carte de visite security » compacte : nom + droits + statut, mais pas comme une « entité domaine de l'utilisateur ».

Erreur n°5 : conclure « le contrôleur n'a pas été appelé — donc c'est un bug dans le contrôleur ».
Si un endpoint n'est pas atteint, le débutant va souvent directement dans le contrôleur, pose un System.out.println("reached") et ne comprend pas pourquoi il n'apparaît pas. Mais dans cette leçon on construit justement le modèle où une requête peut être stoppée dans les filtres avant MVC. Donc « le contrôleur n'a pas été appelé » n'est pas un mystère, c'est un fait diagnostique important : la couche security a pu terminer le traitement plus tôt, et c'est une partie normale de l'architecture.

1
Mission
Spring Security, niveau 3, leçon 3
Bloqué
Remplissage manuel du SecurityContext avec une authentification anonyme
Remplissage manuel du SecurityContext avec une authentification anonyme
1
Mission
Spring Security, niveau 3, leçon 3
Bloqué
Utilisateur actuel dans le contrôleur via Principal et SecurityContextHolder
Utilisateur actuel dans le contrôleur via Principal et SecurityContextHolder
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION