Spring Security
Aujourd'hui, tu vas découvrir un sous-projet de Spring : Spring Security ! Ce framework va te permettre d'intégrer des mécanismes d'authentification et d'autorisation dans tes projets.
Objectifs
- S'initier au fonctionnement de Spring Security
- Configurer le framework
- Gérer les utilisateurs et leurs autorisations
Spring Initializr
Ce n'est pas ton premier rodéo, tu sais comment tout ça va commencer : rends-toi sur https://start.spring.io/
Crée un nouveau projet avec les informations suivantes :
-
"Group" : com.wildcodeschool
-
"Artefact" : myProjectWithSecurity
-
"Dependencies" : Web et Security
Télécharge le zip généré, extrais-le et importe-le dans ton IDE.
Afin d'avoir au moins une route testable, tu peux mettre en place un point d'entrée avec le contrôleur suivant dans le package com.wildcodeschool.myProjectWithSecurity.controllers :
package com.wildcodeschool.myProjectWithSecurity.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@GetMapping("/")
public String hello() {
return "Hello World!!!";
}
}
Lance maintenant ton projet avec la commande ./mvnw spring-boot:run, et ouvre l'url suivante dans ton navigateur :
Là, ce n'est pas comme d'habitude... Au lieu de voir le message "Hello World!!!", tu devrais voir maintenant un formulaire te demandant un nom d'utilisateur et un mot de passe. Tu noteras en passant que tu as été redirigé vers cette url :
Fichtre, la porte est verrouillée... Si tu regardes attentivement le flot de texte dans ta console, tu devrais trouver une ligne qui ressemble à celle-ci :
Using generated security password: #######-####-####-####-############
Essaie de rentrer ce mot de passe généré avec le nom d'utilisateur user dans le formulaire de connexion : cela t'ouvre la porte et te ramène sur la page d'accueil. Par défaut, Spring Security te crée donc un utilisateur user avec un mot de passe généré au lancement de l'application.
Ouvre maintenant l'url suivante pour te déconnecter :
C'est bien gentil tout ça, mais quand je veux développer une API, je ne peux pas passer par un formulaire de connexion...
C'est juste, mais comme la vie est bien faite, tu peux intégrer les identifiants directement dans une requête : tu vas le vérifier avec Postman. D'abord, lance une requête GET sur http://localhost:8080/ : tu auras comme réponse une erreur 401 -- accès non autorisé.
Va maintenant dans l'onglet "Authorization", choisis comme type "Basic Auth" et renseigne les champs "Username" et "Password".

Sauf que, pour l'instant, tu as un nom d'utilisateur et un mot de passe imposés par Spring : tu vas devoir bidouiller un peu la configuration pour aller plus loin.
Configurer Spring Security
Pour configurer Spring Security selon tes envies, tu as besoin d'un objet de configuration qui active la sécurité. Bref, une classe avec l'annotation @Configuration et @EnableWebSecurity: cet objet va décrire la configuration pour la sécurité de ton projet.
package com.wildcodeschool.myProjectWithSecurity.config;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableWebSecurity
public class MySecurityConfig {
}
Pour configurer la chaine de sécurité, il faut créer un @Bean renvoyant une SecurityFilterChain. Commençons avec celle qui prend en paramètre une instance de type HttpSecurity. En fouillant dans la documentation officielle -- regarde dans les ressources, tu trouveras son implémentation par défaut :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> {
try {
authz
.anyRequest().authenticated()
.and().formLogin()
.and().httpBasic();
} catch (Exception e) {
e.printStackTrace();
}
});
return http.build();
}
L'instance http te permet d'ajouter des blocs de paramètres à ta configuration, chaque bloc suivant étant ajouté avec la méthode and(). L'implémentation par défaut est donc à lire comme ceci :
- Sur la configuration pour les requêtes
http, j'ajoute :
un bloc authorizeRequests() pour activer la sécurité ; je précise que :pour anyRequest() -- n'importe quelle requête,je veux que l'utilisateur soit authenticated() -- authentifié,
and()-- un autre bloc :
pour préciser que l'authentification passe par un formLogin() -- un formulaire de connexion,
and()-- un autre bloc :
pour configurer httpBasic() -- l'authentification basique HTTP.
Un test que tu peux faire pour rendre cela plus concret est d'enlever tout le bloc authorizeRequests :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin()
.and().httpBasic();
return http.build();
}
Relance ton application, et ouvre d'abord l'url :
Maintenant que tu es sûr d'être déconnecté, ouvre l'url :
Tu peux y accéder sans connexion parce que tu as enlevé un bloc de la configuration par défaut . C'était le bloc qui précisait que l'accès sur chaque requête devait être authentifié :
authorizeRequests().anyRequest().authenticated()
Tu peux faire des tests en enlevant/rajoutant des blocs, et chercher d'autres options. Rétablis la configuration par défaut pour la suite :
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> {
try {
authz
.anyRequest().authenticated()
.and().formLogin()
.and().httpBasic();
} catch (Exception e) {
e.printStackTrace();
}
});
return http.build();
}
Gérer les utilisateurs
Pour l'instant, tu es bloqué avec ton utilisateur user et son mot de passe généré. Il est temps de redéfinir un autre Bean InMemoryUserDetailsManager -- mais attends la suite avant de la tester :
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User
.withUsername("user")
.password("password")
.roles("")
.build();
return new InMemoryUserDetailsManager(user);
}
Cette déclaration permet de déclarer des utilisateurs "en mémoire" -- c'est-à-dire sans base de données. Avec ce code, tu es en train d'associer l'identifiant user au mot de passe password. Ce n'est pas un mot de passe très recommandé, mais ça nous ira bien pour tester. Tu peux relancer ton application, et aller au formulaire de login... mais password ne marchera pas !
Avec Spring 4, il était possible de stocker des mots de passe en clair. Avec Spring 5, les choses sont un peu plus subtiles : un décodeur par défaut est utilisé. Avant de tester, complète la méthode en utilisant un encodeur par défaut :
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User
.withUsername("user")
.password(encoder.encode("password"))
.roles("USER")
.build();
Note que tu es obligé d'associer des rôles à ton utilisateur : pour l'instant, une chaîne de caractères vide fera l'affaire.
Relance, va à la page de login, et connecte-toi :)
Gérer les autorisations
Que dirais-tu de démarrer un petit back office ? Rajoute une route à ton contrôleur :
@GetMapping("/admin")
public String admin() {
return "Hello Admin!!!";
}
Le back office ne devrait être accessible qu'à un administrateur. Ajoute-le dans les utilisateurs déclarés :
@Bean
public InMemoryUserDetailsManager userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User
.withUsername("user")
.password(encoder.encode("password"))
.roles("")
.build();
UserDetails admin = User
.withUsername("admin")
.password(encoder.encode("12345678"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(List.of(user, admin));
}
Reste maintenant à adapter la gestion des requêtes HTTP -- ton SecurityFilterChain filterChain(HttpSecurity http). Je pourrais t'expliquer comment faire, mais, puisque tu es arrivé aussi loin, j'ai mieux à te proposer. Tu trouveras ci-dessous une ressource qui te permettra de finir seul cette partie. Vois cela comme un test avant de te recruter pour une mission un peu "sensible". Je t'en dirai plus sur cette mission dans la partie challenge.
Challenge
Stratégie, Habileté, Intervention, Exécution et Logistique Défensive

Bravo, je savais que tu étais la personne de la situation. Je peux maintenant te parler de ta mission. Le SHIELD a découvert des accès mal sécurisés sur sa plateforme web. Le directeur veut refondre tout le projet, et m'a demandé de confier la réalisation d'un POC à quelqu'un de confiance. Tu vas créer des routes selon les critères suivants :
-
/: la route est accessible par tout le monde, sans authentification, et retourne le message "Welcome to the SHIELD". -
/avengers/assemble: la route est accessible uniquement aux champions du SHIELD, et retourne le message "Avengers... Assemble". -
/secret-bases: cette page n'est accessible que par le directeur du SHIELD, et retourne la liste de toutes les villes où il y a une Wild Code School (regarde cette page pour t'aider).
Ton projet doit créer des identifiants pour les utilisateurs suivants :
-
Nom : Steve | Mot de passe : motdepasse | Rôle : CHAMPION
-
Nom : Nick | Mot de passe : flerken | Rôle : DIRECTOR
Critères de validation
- Le projet implémente les routes demandées.
- Les identifiants permettent d'accéder aux routes sécurisées.
- Le code est disponible sur GitHub.
Ressources partagées sur cette quête
- Il explique parfaitement comment attribuer des autorisations en fonction des rôles.https://www.youtube.com/watch?v=tYpS8BmrEyo&t=170s
- Pour la fin de l'exercice étant donné que le lien fourni est broken !https://www.baeldung.com/java-config-spring-security
- Pour configurer hhtpsecurity (en français)https://blog.ouidou.fr/spring-security-pour-les-nuls-1930fce60089