Bonjour ! Dans la leçon précédente, nous avons implémenté une logique métier complexe pour la création de commandes, en découvrant l'importance de la couche service et de l'annotation @Transactional. Notre API est maintenant fonctionnelle, mais elle a une faille de sécurité majeure : elle est complètement ouverte, n'importe qui peut y accéder.
Aujourd'hui, nous allons corriger cela en nous plongeant dans un des aspects les plus importants du développement backend : la sécurité. L'objectif de cette leçon est d'apprendre à configurer Spring Security pour protéger les endpoints de notre API. Nous commencerons par une méthode d'authentification simple mais fondamentale, le Basic Authentication, qui nous servira de base pour des mécanismes plus avancés.
1. Introduction à Spring Security et Basic Auth
Avant de protéger notre API, il est essentiel de comprendre pourquoi c'est nécessaire et comment fonctionne le mécanisme que nous allons utiliser. Une API non protégée est une porte ouverte aux accès non autorisés et à diverses attaques.
Sécurisez vos API avec Spring Security : Basic Auth - sfeir.dev
Cet article de SFEIR Dev pose parfaitement le contexte.
Lisez les deux premières sections : "Pourquoi sécuriser une API ?" et "Définition de Basic Auth". Retenez bien que le Basic Auth transmet un nom d'utilisateur et un mot de passe dans chaque requête et que l'encodage Base64 n'est pas une forme de chiffrement.
Pour mettre en place cette sécurité, la première étape est d'ajouter la dépendance Spring Security à notre projet.
<!-- Dans votre fichier pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Une fois cette dépendance ajoutée à votre pom.xml et votre projet rafraîchi, redémarrez votre application. C'est là que la "magie" de Spring Boot opère.
Le comportement par défaut : Sécurisé par défaut
Essayez maintenant d'appeler un de vos endpoints existants (par exemple, GET /api/products) avec Postman. Vous ne recevrez plus une liste de produits, mais une réponse 401 Unauthorized.
Spring Security applique un principe de "sécurité par défaut". Dès qu'il est présent, il verrouille tous les endpoints de votre application. Pour y accéder, il faut maintenant s'authentifier.
Mais avec quels identifiants ? Regardez les logs de votre console au démarrage de l'application. Vous devriez y voir un message similaire à celui-ci :
Using generated security password: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Spring a généré un mot de passe temporaire pour un utilisateur par défaut nommé user. Utilisons-le.
Dans Postman, allez dans l'onglet "Authorization", sélectionnez "Basic Auth" et entrez user comme nom d'utilisateur et le mot de passe généré.

Relancez la requête. Cette fois, vous devriez recevoir la réponse attendue (la liste des produits). Vous venez de faire votre première requête authentifiée !
2. Création d'une configuration de sécurité personnalisée
Le comportement par défaut est un excellent point de départ, mais en production, nous avons besoin de plus de contrôle :
- Définir nos propres utilisateurs et mots de passe.
- Rendre certains endpoints publics (par exemple, la page d'accueil ou la liste des produits).
- Laisser les autres endpoints sécurisés.
Pour cela, nous devons créer une classe de configuration dédiée. Créez une nouvelle classe SecurityConfig dans un package config.
package com.example.ecommerce.config;
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
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// La configuration viendra ici
return http.build();
}
}
L'annotation @Configuration indique à Spring que cette classe contient des définitions de beans. Le bean le plus important ici est le SecurityFilterChain.
La vidéo suivante explique très bien la mise en place de cette classe et le rôle du SecurityFilterChain.
Sécuriser une application Spring Boot 3 avec Spring Security 6 | Authentification et Autorisations
La vidéo "Sécuriser une application Spring Boot 3" de la chaîne Autour Du Code montre la transition de la configuration par défaut à une classe de configuration personnalisée.
Regardez la partie sur la création de la classe de configuration. L'auteur y crée une classe SecurityConfig et définit un bean SecurityFilterChain. C'est la structure de base que nous allons utiliser.
Maintenant, configurons notre chaîne de filtres pour répondre à nos besoins. Nous utiliserons une syntaxe fluide (fluent API) pour enchaîner les règles.
// Dans la classe SecurityConfig
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 1. Désactiver CSRF
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/products/**").permitAll() // 2. Rendre la consultation des produits publique
.requestMatchers("/api/orders/**").authenticated() // 3. Exiger une authentification pour les commandes
.anyRequest().authenticated() // 4. Sécuriser tout le reste
)
.httpBasic(Customizer.withDefaults()); // 5. Activer le Basic Auth
return http.build();
}
Analysons cette configuration :
csrf(csrf -> csrf.disable()): La protection CSRF (Cross-Site Request Forgery) est cruciale pour les applications web traditionnelles basées sur les sessions. Pour les API REST "stateless" (sans état) que nous construisons, elle est généralement désactivée.requestMatchers("/api/products/**").permitAll(): On autorise l'accès public (permitAll) à tous les endpoints qui commencent par/api/products. N'importe qui peut voir les produits.requestMatchers("/api/orders/**").authenticated(): On exige une authentification (authenticated) pour passer une commande.anyRequest().authenticated(): C'est une règle de sécurité. Toute autre requête (anyRequest) qui n'a pas été spécifiquement autorisée avant doit être authentifiée.httpBasic(Customizer.withDefaults()): On active explicitement la stratégie d'authentification HTTP Basic. Spring saura qu'il doit chercher un en-têteAuthorization: Basic ....
3. Définir des utilisateurs et gérer les mots de passe
Notre configuration fonctionne, mais elle utilise toujours l'utilisateur user généré par défaut. Nous allons maintenant définir nos propres utilisateurs. Pour cela, nous devons dire à Spring Security comment trouver les utilisateurs et comment vérifier leurs mots de passe.
Pour comprendre les composants impliqués, la vidéo suivante offre une excellente vue d'ensemble de l'architecture de base.
Sécuriser une application Spring Boot 3 avec Spring Security 6 | Authentification et Autorisations
Retournons à la vidéo d'Autour Du Code pour une explication conceptuelle.
Regardez le segment qui explique l'architecture de Spring Security. Concentrez-vous sur deux interfaces clés : UserDetailsService: Son rôle est de charger les détails d'un utilisateur à partir de son nom d'utilisateur. PasswordEncoder: Son rôle est de crypter (hacher) les mots de passe pour un stockage sécurisé et de les comparer lors de l'authentification.
Le PasswordEncoder
On ne stocke jamais les mots de passe en clair. On stocke une version "hachée". BCrypt est l'algorithme de hachage standard recommandé par Spring. Ajoutons un bean pour cela dans notre SecurityConfig.
// Dans SecurityConfig.java
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// ...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Le UserDetailsService
Pour commencer, nous allons stocker nos utilisateurs en mémoire. C'est idéal pour les tests et les démos. Spring fournit une implémentation prête à l'emploi : InMemoryUserDetailsManager.
Ajoutons un autre bean à SecurityConfig pour définir nos utilisateurs.
// Dans SecurityConfig.java
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
// ...
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
// Création d'un utilisateur standard
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder.encode("password123")) // Mot de passe haché !
.roles("USER")
.build();
// Création d'un administrateur
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("admin123")) // Mot de passe haché !
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
Points importants :
- Nous injectons notre bean
PasswordEncoderpour hacher les mots de passe (passwordEncoder.encode(...)). Si vous oubliez cette étape, l'authentification échouera car Spring comparera un mot de passe haché (venant de la requête) avec un mot de passe en clair (stocké en mémoire). - Nous définissons des rôles (
.roles("USER")). Même si nous ne les utilisons pas encore pour la restriction d'accès, c'est une bonne pratique de les définir dès maintenant. Cela prépare le terrain pour la prochaine leçon.
Votre classe SecurityConfig est maintenant complète. Redémarrez l'application. Le mot de passe généré ne devrait plus apparaître. Vous pouvez maintenant vous authentifier avec user/password123 ou admin/admin123.
Essayez d'accéder à /api/orders :
- Sans authentification :
401 Unauthorized(comme prévu). - Avec l'utilisateur
user:200 OK(ou un autre code de succès si la requête est valide). - Avec l'utilisateur
admin:200 OKégalement.
Essayez d'accéder à /api/products :
- Sans authentification :
200 OK(comme prévu, c'est un endpoint public).
Conclusion
Félicitations ! Vous avez mis en place la première ligne de défense de votre application. Vous avez appris à utiliser Spring Security pour passer d'une API complètement ouverte à une API sécurisée avec des règles d'accès claires.
Points clés à retenir :
- Spring Security adopte une approche sécurisée par défaut, verrouillant tous les endpoints dès son ajout.
- La configuration se fait via un bean
SecurityFilterChaindans une classe annotée@Configuration. - La méthode
authorizeHttpRequestspermet de définir des règles précises (permitAll,authenticated) pour chaque URL (requestMatchers). - Les mots de passe doivent toujours être hachés à l'aide d'un
PasswordEncoder(commeBCryptPasswordEncoder). UserDetailsServiceest le service responsable de charger les informations de l'utilisateur pour l'authentification.InMemoryUserDetailsManagerest une implémentation simple et pratique pour débuter.
Dans la prochaine leçon, nous irons plus loin. L'authentification ne suffit pas ; nous avons besoin d'autorisation. Vous apprendrez à utiliser les rôles que nous avons définis (USER, ADMIN) pour restreindre l'accès à certaines fonctionnalités. Par exemple, nous pourrions décider que seuls les utilisateurs avec le rôle ADMIN peuvent ajouter de nouveaux produits.