Pourquoi Securiser une application Spring Boot ?
La sécurité est un aspect crucial pour n'importe quelle application, mais en particulier dans les applications web modernes qui traitent des données sensibles ou des informations personnelles. En tant qu'application Spring Boot, il est essentiel de mettre en place des mesures de sécurité robustes pour protéger contre les menaces potentielles.
Un cas d'utilisation concret serait un système de gestion de contenu (CMS) où des utilisateurs peuvent publier et modifier des articles. Si ce système n'est pas sécurisé, il pourrait être facilement exploité par un attaquant pour publier du contenu malveillant ou même accéder aux informations sensibles.
Prerequis
- Connaissance de base de Java
- Familiarité avec Spring Boot
- Connaissance des concepts fondamentaux d'authentification et d'autorisation
- Installation de Java 11 ou plus tard
- Installation de Maven
- IDE (IntelliJ IDEA, Eclipse)
Concepts fondamentaux
Authentification vs Autorisation
Authentification : L'authentification est le processus par lequel un utilisateur prouve son identité. Elle vérifie que l'utilisateur qui se connecte est bien celui qu'il affirme être.
// Exemple de configuration d'un filter pour l'authentification avec Spring Security
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
Autorisation : L'autorisation est le processus par lequel une fois l'utilisateur authentifié, on détermine les actions qu'il peut effectuer. Cela peut être basé sur des rôles (admin, user) ou des permissions spécifiques.
// Exemple de configuration d'un role-based authorization
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
return http.build();
}
Utilisation de Spring Security
Spring Security est un framework populaire pour la gestion de la sécurité dans les applications Spring. Il fournit des outils pour l'authentification et l'autorisation, ainsi que des fonctionnalités comme le changement de mot de passe, la récupération d'account, etc.
// Exemple de configuration de Spring Security avec un UserDetailsService
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
Mise en pratique : projet fil rouge
Création du Projet
Créer un nouveau projet Spring Boot
- Utiliser le générateur de projets Spring Initializr (https://start.spring.io/)
- Sélectionner Maven Project, Java 11, et ajouter les dépendances suivantes :
- Spring Web
- Spring Security
- H2 Database
Structure du Projet
src/main/java/com/example/demo/ ├── DemoApplication.java ├── controller/ │ └── TaskController.java ├── model/ │ └── Task.java ├── repository/ │ └── TaskRepository.java └── service/ └── TaskService.javaCode Complet
// DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
java
// Task.java
package com.example.demo.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
private boolean completed;
// Getters and Setters
}
java
// TaskRepository.java
package com.example.demo.repository;
import com.example.demo.model.Task;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TaskRepository extends JpaRepository<Task, Long> {
}
java
// TaskService.java
package com.example.demo.service;
import com.example.demo.model.Task;
import com.example.demo.repository.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TaskService {
@Autowired
private TaskRepository taskRepository;
public Iterable<Task> findAll() {
return taskRepository.findAll();
}
public Task save(Task task) {
return taskRepository.save(task);
}
}
java
// TaskController.java
package com.example.demo.controller;
import com.example.demo.model.Task;
import com.example.demo.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping
public Iterable<Task> findAll() {
return taskService.findAll();
}
@PostMapping
public Task save(@RequestBody Task task) {
return taskService.save(task);
}
}
- Configuration de Security
// SecurityConfig.java
package com.example.demo.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.config.annotation.web.configuration.EnableWebSecurity;
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;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
- Commandes à Executer
mvn clean install
java -jar target/demo-0.0.1-SNAPSHOT.jar
Erreurs frequentes et debugging
Erreur 403 Forbidden
Message d'erreur :
Forbidden
Access is denied.
Code Incorrect :
// Code incorrect qui cause une erreur 403
http.authorizeRequests(authorize -> authorize
.anyRequest().permitAll()
);
Code Correct :
// Code correct pour autoriser toutes les requêtes publiques
http.authorizeRequests(authorize -> authorize
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
);
Erreur 401 Unauthorized
Message d'erreur :
Unauthorized
Full authentication is required to access this resource.
Code Incorrect :
// Code incorrect qui cause une erreur 401
http.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
Code Correct :
// Code correct pour authentifier les requêtes
http.authorizeRequests(authorize -> authorize
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
Pour aller plus loin
Utiliser des Tokens JWT
- Ajouter une authentification stateless avec des tokens JSON Web Token (JWT).
Implementer des Rôles et Permissions Avancés
- Utiliser la bibliothèque Spring Security avec les rôles et permissions complexes.
Gestion des Sessions
- Configurer la gestion des sessions pour une meilleure sécurité.
Défi Pratique
Défi : Créer une application Spring Boot qui utilise un service d'authentification externe comme OAuth2 pour gérer l'authentification et l'autorisation.