1. Introduction à Spring Boot
1.1. Qu’est-ce que Spring Boot ?
Spring Boot est une extension du framework Spring qui simplifie le développement d’applications Java en offrant une configuration automatique, des dépendances prépackagées (starters), et un serveur embarqué (Tomcat par défaut). Il est conçu pour réduire le code "boilerplate" et accélérer la mise en production.Exemple conceptuel :
@SpringBootApplication
public class GestionVentesApplication {
public static void main(String[] args) {
SpringApplication.run(GestionVentesApplication.class, args);
}
}
- @SpringBootApplication : Combine @Configuration, @EnableAutoConfiguration, et @ComponentScan.
- SpringApplication.run : Lance l’application avec un serveur embarqué.
1.2. Différence entre Spring Boot et Spring MVC classique
Spring MVC nécessite une configuration manuelle (XML ou Java) et un serveur externe (ex. Tomcat déployé séparément). Spring Boot automatise cela avec des conventions et un serveur intégré.Tableau comparatif :
Aspect | Spring MVC | Spring Boot |
---|---|---|
Configuration | Manuelle | Auto-configurée |
Serveur | Externe (Tomcat) | Embarqué |
Dépendances | Définies manuellement | Starters |
Démarrage | Déploiement WAR | JAR exécutable |
1.3. Avantages de Spring Boot
Explication :
- Convention sur configuration : Réduit le besoin de configuration explicite.
- Starters : Simplifie la gestion des dépendances (ex. spring-boot-starter-web).
- Serveur embarqué : Pas besoin d’installer un serveur séparé.
- Production-ready : Outils comme Actuator pour la supervision.
Exemple de starter dans pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.4. Architecture de base d’une application Spring Boot
Une application Spring Boot suit une architecture typique :- Couche présentation : Contrôleurs REST ou pages web.
- Couche métier : Services pour la logique.
- Couche données : Repositories pour accéder à la base (MySQL ici).
- Configuration : Gérée via application.properties ou application.yml.
Structure initiale du projet :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.ventes/
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
Projet CRUD : Gestion des ventes - Premiers pas
Objectif : Construire une application pour gérer des ventes avec les entités suivantes :
- Categorie : id, nom, description.
- Produit : id, idCategorie, nom, prix.
- Client : id, nom, email, password, status.
- Commande : id, idProduit, idClient, date, quantite.
Étape actuelle : Comprendre Spring Boot avant de configurer le projet.
Structure envisagée :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.ventes/
│ │ │ ├── controller/
│ │ │ ├── model/
│ │ │ ├── repository/
│ │ │ ├── service/
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
Prochaines étapes : Configurer l’environnement et initialiser le projet (section 2).
2. Mise en place de l’environnement
2.1. Prérequis (Java, Maven/Gradle, IDE comme IntelliJ ou Eclipse)
Pour développer avec Spring Boot, vous avez besoin de :- Java : JDK 17 (version moderne recommandée).
- Maven ou Gradle : Outil de gestion de dépendances (nous utiliserons Maven ici).
- IDE : IntelliJ IDEA ou Eclipse pour faciliter le développement.
Étapes :
- Installez Java (java -version pour vérifier).
- Installez Maven (mvn -version pour vérifier).
- Téléchargez et configurez un IDE (ex. IntelliJ IDEA Community).
2.2. Création d’un projet avec Spring Initializr
Spring Initializr est un outil en ligne (ou intégré aux IDE) pour générer un projet Spring Boot avec les dépendances nécessaires. Nous allons inclure les starters pour le web, JPA, MySQL, et Thymeleaf.Étapes :
- Allez sur start.spring.io.
- Configurez :
- Projet : Maven.
- Langage : Java.
- Version Spring Boot : 3.2.3 (dernière stable au 28/02/2025, supposée).
- Group : com.exemple.
- Artifact : gestion-ventes.
- Java : 17.
- Dépendances : Spring Web, Spring Data JPA, MySQL Driver, Thymeleaf.
- Générez et téléchargez le ZIP, puis décompressez-le.
2.3. Structure d’un projet Spring Boot
Le projet généré suit une structure standard :- src/main/java : Code source Java.
- src/main/resources : Fichiers de configuration et ressources statiques.
- pom.xml : Gestion des dépendances.
Structure initiale :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ ├── static/ # Fichiers CSS, JS
│ │ ├── templates/ # Fichiers Thymeleaf
│ │ └── application.properties
│ └── test/
├── pom.xml
Contenu de pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exemple</groupId>
<artifactId>gestion-ventes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web pour API REST et Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA pour la persistance -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Thymeleaf pour les vues -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Tests -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- <parent> : Hérite des configurations de Spring Boot.
- Dépendances : Incluent les starters nécessaires.
- spring-boot-maven-plugin : Permet de générer un JAR exécutable.
2.4. Configuration de base (fichier application.properties/application.yml)
Le fichier application.properties configure l’application, notamment la connexion à MySQL.Configuration MySQL - src/main/resources/application.properties :
# Port de l'application
server.port=8080
# Configuration MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/gestion_ventes?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=votre_mot_de_passe
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate/JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# Thymeleaf
spring.thymeleaf.cache=false
Étapes pour MySQL :
- Installez MySQL (ex. via Docker ou localement).
- Créez une base gestion_ventes :
sql
CREATE DATABASE gestion_ventes;
- Mettez à jour username et password selon votre configuration MySQL.
Explication :
- spring.datasource.* : Configure la connexion à MySQL.
- spring.jpa.hibernate.ddl-auto=update : Met à jour le schéma automatiquement.
- spring.thymeleaf.cache=false : Désactive le cache pour le développement.
Alternative en YAML - application.yml :
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/gestion_ventes?createDatabaseIfNotExist=true
username: root
password: votre_mot_de_passe
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
thymeleaf:
cache: false
Projet CRUD : Gestion des ventes - Configuration initiale
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
Classe principale - GestionVentesApplication.java :
package com.exemple.ventes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GestionVentesApplication {
public static void main(String[] args) {
SpringApplication.run(GestionVentesApplication.class, args);
}
}
Test initial :
- Lancez MySQL et créez la base gestion_ventes.
- Exécutez l’application :
mvn spring-boot:run
- Vérifiez que le serveur démarre sur http://localhost:8080.
3. Les fondamentaux de Spring Boot
3.1. Auto-configuration et starters
Spring Boot utilise l’auto-configuration pour configurer automatiquement les composants en fonction des dépendances (starters) présentes dans le projet. Par exemple, spring-boot-starter-web configure un serveur Tomcat embarqué et Spring MVC, tandis que spring-boot-starter-data-jpa configure Hibernate.Exemple :
Avec les starters déjà inclus dans pom.xml (web, JPA, MySQL, Thymeleaf), Spring Boot détecte MySQL et configure une datasource sans code supplémentaire.
3.2. Création d’un premier contrôleur REST
Un contrôleur REST gère les requêtes HTTP et renvoie des réponses au format JSON. Nous allons créer un contrôleur simple pour tester l’application avant d’implémenter le CRUD.Contrôleur - src/main/java/com/exemple/ventes/controller/TestController.java :
package com.exemple.ventes.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/hello")
public String direBonjour() {
return "Bonjour depuis Spring Boot !";
}
}
- @RestController : Combine @Controller et @ResponseBody, indiquant que les réponses sont au format JSON (ou texte ici).
- @GetMapping("/hello") : Mappe la requête GET /hello à cette méthode.
Test :
- Lancez l’application avec mvn spring-boot:run.
- Ouvrez un navigateur ou utilisez curl : http://localhost:8080/hello.
- Résultat attendu : "Bonjour depuis Spring Boot !".
3.3. Gestion des dépendances avec Maven/Gradle
Maven gère les dépendances via pom.xml. Les starters simplifient ce processus en regroupant des bibliothèques courantes. Nous avons déjà configuré les dépendances nécessaires dans la section 2.Ajout d’une dépendance utile - Lombok (optionnel) :
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- Lombok : Réduit le code boilerplate (getters, setters) avec des annotations comme @Data.
3.4. Démarrage de l’application (serveur embarqué : Tomcat, Jetty)
Spring Boot inclut un serveur embarqué (Tomcat par défaut) lancé via SpringApplication.run. Pas besoin de déployer un WAR comme avec Spring MVC classique.Classe principale (rappel) - GestionVentesApplication.java :
package com.exemple.ventes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GestionVentesApplication {
public static void main(String[] args) {
SpringApplication.run(GestionVentesApplication.class, args);
}
}
Changer de serveur (optionnel, ex. Jetty) :
- Exclure Tomcat et ajouter Jetty dans pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
graph TD
A[Démarrage] --> B(SpringApplication.run)
B --> C[Serveur Embarqué]
C --> D[Tomcat par défaut]
C --> E[Jetty optionnel]
A --> F[@SpringBootApplication]
Projet CRUD : Gestion des ventes - Progression
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ ├── controller/
│ │ │ │ └── TestController.java
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
Ajout des entités de base :
- Categorie - src/main/java/com/exemple/ventes/model/Categorie.java :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Categorie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private String description;
}
- Produit - src/main/java/com/exemple/ventes/model/Produit.java :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
@Entity
@Data
public class Produit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Categorie categorie;
private String nom;
private double prix;
}
- Client - src/main/java/com/exemple/ventes/model/Client.java :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private String email;
private String password;
private String status; // ex. "actif", "inactif"
}
- Commande - src/main/java/com/exemple/ventes/model/Commande.java :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
import java.time.LocalDateTime;
@Entity
@Data
public class Commande {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Produit produit;
@ManyToOne
private Client client;
private LocalDateTime date;
private int quantite;
}
- @Entity : Marque la classe comme une entité JPA.
- @Id et @GeneratedValue : Définit une clé primaire auto-incrémentée.
- @ManyToOne : Relation entre entités (ex. un produit appartient à une catégorie).
- @Data (Lombok) : Génère getters, setters, toString, etc.
Test initial :
- Lancez l’application (mvn spring-boot:run).
- Vérifiez les logs : Hibernate crée les tables dans MySQL (gestion_ventes).
4. Développement d’API REST
4.1. Annotations REST (@RestController, @GetMapping, etc.)
Spring Boot facilite la création d’API REST avec des annotations comme @RestController pour définir un contrôleur REST et @GetMapping, @PostMapping, etc., pour mapper les méthodes HTTP aux endpoints.4.2. Gestion des méthodes HTTP (GET, POST, PUT, DELETE)
Nous implémenterons un CRUD complet pour l’entité Categorie avec les méthodes HTTP correspondantes :- GET : Lister ou récupérer une catégorie.
- POST : Ajouter une catégorie.
- PUT : Mettre à jour une catégorie.
- DELETE : Supprimer une catégorie.
Repository - src/main/java/com/exemple/ventes/repository/CategorieRepository.java :
package com.exemple.ventes.repository;
import com.exemple.ventes.model.Categorie;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CategorieRepository extends JpaRepository<Categorie, Long> {
}
Contrôleur REST - src/main/java/com/exemple/ventes/controller/CategorieController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Categorie;
import com.exemple.ventes.repository.CategorieRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/categories")
public class CategorieController {
@Autowired
private CategorieRepository categorieRepository;
// GET - Lister toutes les catégories
@GetMapping
public List<Categorie> listerCategories() {
return categorieRepository.findAll();
}
// GET - Récupérer une catégorie par ID
@GetMapping("/{id}")
public ResponseEntity<Categorie> obtenirCategorie(@PathVariable Long id) {
return categorieRepository.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
// POST - Ajouter une catégorie
@PostMapping
public Categorie ajouterCategorie(@RequestBody Categorie categorie) {
return categorieRepository.save(categorie);
}
// PUT - Mettre à jour une catégorie
@PutMapping("/{id}")
public ResponseEntity<Categorie> mettreAJourCategorie(@PathVariable Long id, @RequestBody Categorie categorie) {
if (!categorieRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
categorie.setId(id);
Categorie updatedCategorie = categorieRepository.save(categorie);
return ResponseEntity.ok(updatedCategorie);
}
// DELETE - Supprimer une catégorie
@DeleteMapping("/{id}")
public ResponseEntity<Void> supprimerCategorie(@PathVariable Long id) {
if (!categorieRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
categorieRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
- @RequestMapping("/api/categories") : Préfixe tous les endpoints avec /api/categories.
- @GetMapping : Liste toutes les catégories ou récupère une catégorie spécifique.
- @PostMapping : Crée une nouvelle catégorie à partir du corps JSON.
- @PutMapping : Met à jour une catégorie existante.
- @DeleteMapping : Supprime une catégorie.
- ResponseEntity : Permet de gérer les codes HTTP (200, 404, 204, etc.).
4.3. Passage de paramètres (@PathVariable, @RequestParam, @RequestBody)
Spring Boot gère les paramètres de requête de trois façons principales :- @PathVariable : Extrait une valeur de l’URL (ex. /api/categories/1).
- @RequestParam : Récupère les paramètres de requête (ex. ?nom=Electronics).
- @RequestBody : Lie le corps JSON de la requête à un objet.
Ajout d’un endpoint avec @RequestParam dans CategorieController.java :
@GetMapping("/recherche")
public List<Categorie> rechercherParNom(@RequestParam String nom) {
return categorieRepository.findAll().stream()
.filter(c -> c.getNom().toLowerCase().contains(nom.toLowerCase()))
.toList();
}
Explication :
- @RequestParam : Récupère le paramètre nom de l’URL (ex. /api/categories/recherche?nom=elec).
- Filtre les catégories par nom (exemple simplifié, en production, utiliser une requête JPA).
Exemple de requêtes :
- GET : curl http://localhost:8080/api/categories
- GET par ID : curl http://localhost:8080/api/categories/1
- POST : curl -X POST -H "Content-Type: application/json" -d '{"nom":"Electronics","description":"Appareils électroniques"}' http://localhost:8080/api/categories
- PUT : curl -X PUT -H "Content-Type: application/json" -d '{"nom":"Electronics Updated","description":"Updated"}' http://localhost:8080/api/categories/1
- DELETE : curl -X DELETE http://localhost:8080/api/categories/1
- GET avec param : curl http://localhost:8080/api/categories/recherche?nom=elec
4.4. Réponses HTTP et codes de statut
Les API REST doivent renvoyer des codes HTTP appropriés :- 200 OK : Succès.
- 201 Created : Ressource créée (optionnel pour POST).
- 204 No Content : Suppression réussie.
- 404 Not Found : Ressource introuvable.
Modification du POST pour retourner 201 :
@PostMapping
public ResponseEntity<Categorie> ajouterCategorie(@RequestBody Categorie categorie) {
Categorie savedCategorie = categorieRepository.save(categorie);
return ResponseEntity.status(201).body(savedCategorie);
}
Explication :
- ResponseEntity.status(201) : Définit le code HTTP 201 pour indiquer une création réussie.
Projet CRUD : Gestion des ventes - Progression
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ ├── controller/
│ │ │ │ ├── TestController.java
│ │ │ │ └── CategorieController.java
│ │ │ ├── model/
│ │ │ │ ├── Categorie.java
│ │ │ │ ├── Produit.java
│ │ │ │ ├── Client.java
│ │ │ │ └── Commande.java
│ │ │ ├── repository/
│ │ │ │ └── CategorieRepository.java
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
État actuel :
- API REST pour Categorie implémentée avec CRUD complet :
- GET /api/categories : Liste toutes les catégories.
- GET /api/categories/{id} : Récupère une catégorie.
- POST /api/categories : Ajoute une catégorie.
- PUT /api/categories/{id} : Met à jour une catégorie.
- DELETE /api/categories/{id} : Supprime une catégorie.
- Les tables sont créées dans MySQL via Hibernate.
Test :
- Lancez l’application (mvn spring-boot:run).
- Testez les endpoints avec curl ou Postman (exemples ci-dessus).
Prochaines étapes :
- Étendre le CRUD aux autres entités (Produit, Client, Commande) dans la section 5 avec Spring Data.
5. Gestion des données avec Spring Data
5.1. Introduction à Spring Data JPA
Spring Data JPA simplifie l’accès aux données en fournissant une couche d’abstraction au-dessus de JPA (Java Persistence API). Il génère automatiquement des implémentations pour les interfaces de repository, offrant des méthodes CRUD prêtes à l’emploi.Avantages :
- Réduction du code boilerplate.
- Méthodes prédéfinies (findAll, save, deleteById, etc.).
- Support des requêtes personnalisées via conventions de nommage.
5.2. Configuration d’une base de données (H2, MySQL, PostgreSQL)
Nous utilisons déjà MySQL, configuré dans application.properties. Voici un rappel :spring.datasource.url=jdbc:mysql://localhost:3306/gestion_ventes?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=votre_mot_de_passe
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
Note : Pour H2 (in-memory), il suffirait de remplacer par :
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
5.3. Création d’entités et repositories
Nous avons déjà les entités (Categorie, Produit, Client, Commande). Ajoutons les relations et les repositories pour toutes les entités.Entités mises à jour :
- Categorie.java (inchangé) :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Categorie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
private String description;
}
- Produit.java (avec relation) :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
@Entity
@Data
public class Produit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Categorie categorie;
private String nom;
private double prix;
}
- Client.java (avec validation) :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Entity
@Data
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Le nom est requis")
private String nom;
@Email(message = "Email invalide")
private String email;
@NotBlank(message = "Le mot de passe est requis")
private String password;
private String status; // ex. "actif", "inactif"
}
- Commande.java (avec relations) :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
import java.time.LocalDateTime;
@Entity
@Data
public class Commande {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Produit produit;
@ManyToOne
private Client client;
private LocalDateTime date;
private int quantite;
}
Repositories :
- ProduitRepository.java :
package com.exemple.ventes.repository;
import com.exemple.ventes.model.Produit;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProduitRepository extends JpaRepository<Produit, Long> {
}
- ClientRepository.java :
package com.exemple.ventes.repository;
import com.exemple.ventes.model.Client;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ClientRepository extends JpaRepository<Client, Long> {
}
- CommandeRepository.java :
package com.exemple.ventes.repository;
import com.exemple.ventes.model.Commande;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CommandeRepository extends JpaRepository<Commande, Long> {
}
Explication :
- Les relations @ManyToOne lient Produit à Categorie et Commande à Produit/Client.
- Les repositories héritent de JpaRepository, fournissant des méthodes CRUD.
5.4. Opérations CRUD avec Spring Data
Nous allons créer des contrôleurs REST pour chaque entité avec des opérations CRUD complètes.Contrôleur Produit - src/main/java/com/exemple/ventes/controller/ProduitController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Produit;
import com.exemple.ventes.repository.ProduitRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/produits")
public class ProduitController {
@Autowired
private ProduitRepository produitRepository;
@GetMapping
public List<Produit> listerProduits() {
return produitRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Produit> obtenirProduit(@PathVariable Long id) {
return produitRepository.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Produit> ajouterProduit(@RequestBody Produit produit) {
Produit savedProduit = produitRepository.save(produit);
return ResponseEntity.status(201).body(savedProduit);
}
@PutMapping("/{id}")
public ResponseEntity<Produit> mettreAJourProduit(@PathVariable Long id, @RequestBody Produit produit) {
if (!produitRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
produit.setId(id);
Produit updatedProduit = produitRepository.save(produit);
return ResponseEntity.ok(updatedProduit);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> supprimerProduit(@PathVariable Long id) {
if (!produitRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
produitRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
Contrôleur Client - src/main/java/com/exemple/ventes/controller/ClientController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Client;
import com.exemple.ventes.repository.ClientRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/clients")
public class ClientController {
@Autowired
private ClientRepository clientRepository;
@GetMapping
public List<Client> listerClients() {
return clientRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Client> obtenirClient(@PathVariable Long id) {
return clientRepository.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Client> ajouterClient(@Valid @RequestBody Client client) {
Client savedClient = clientRepository.save(client);
return ResponseEntity.status(201).body(savedClient);
}
@PutMapping("/{id}")
public ResponseEntity<Client> mettreAJourClient(@PathVariable Long id, @Valid @RequestBody Client client) {
if (!clientRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
client.setId(id);
Client updatedClient = clientRepository.save(client);
return ResponseEntity.ok(updatedClient);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> supprimerClient(@PathVariable Long id) {
if (!clientRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
clientRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
Contrôleur Commande - src/main/java/com/exemple/ventes/controller/CommandeController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Commande;
import com.exemple.ventes.repository.CommandeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/commandes")
public class CommandeController {
@Autowired
private CommandeRepository commandeRepository;
@GetMapping
public List<Commande> listerCommandes() {
return commandeRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Commande> obtenirCommande(@PathVariable Long id) {
return commandeRepository.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Commande> ajouterCommande(@RequestBody Commande commande) {
commande.setDate(LocalDateTime.now()); // Ajoute la date courante
Commande savedCommande = commandeRepository.save(commande);
return ResponseEntity.status(201).body(savedCommande);
}
@PutMapping("/{id}")
public ResponseEntity<Commande> mettreAJourCommande(@PathVariable Long id, @RequestBody Commande commande) {
if (!commandeRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
commande.setId(id);
Commande updatedCommande = commandeRepository.save(commande);
return ResponseEntity.ok(updatedCommande);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> supprimerCommande(@PathVariable Long id) {
if (!commandeRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
commandeRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
Explication :
- Chaque contrôleur utilise son repository pour les opérations CRUD.
- @Valid sur ClientController active la validation des champs annotés dans Client.
- LocalDateTime.now() dans CommandeController définit la date automatiquement.
Projet CRUD : Gestion des ventes - Progression
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ ├── controller/
│ │ │ │ ├── TestController.java
│ │ │ │ ├── CategorieController.java
│ │ │ │ ├── ProduitController.java
│ │ │ │ ├── ClientController.java
│ │ │ │ └── CommandeController.java
│ │ │ ├── model/
│ │ │ │ ├── Categorie.java
│ │ │ │ ├── Produit.java
│ │ │ │ ├── Client.java
│ │ │ │ └── Commande.java
│ │ │ ├── repository/
│ │ │ │ ├── CategorieRepository.java
│ │ │ │ ├── ProduitRepository.java
│ │ │ │ ├── ClientRepository.java
│ │ │ │ └── CommandeRepository.java
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ └── application.properties
├── pom.xml
État actuel :
- API REST complète pour toutes les entités :
- /api/categories (CRUD pour Categorie).
- /api/produits (CRUD pour Produit).
- /api/clients (CRUD pour Client avec validation).
- /api/commandes (CRUD pour Commande).
Test :
- Lancez l’application (mvn spring-boot:run).
- Testez avec curl ou Postman :
- Ajouter une catégorie : curl -X POST -H "Content-Type: application/json" -d '{"nom":"Electronics","description":"Gadgets"}' http://localhost:8080/api/categories
- Ajouter un produit : curl -X POST -H "Content-Type: application/json" -d '{"categorie":{"id":1},"nom":"TV","prix":500.0}' http://localhost:8080/api/produits
- Ajouter un client : curl -X POST -H "Content-Type: application/json" -d '{"nom":"Alice","email":"alice@example.com","password":"1234","status":"actif"}' http://localhost:8080/api/clients
- Ajouter une commande : curl -X POST -H "Content-Type: application/json" -d '{"produit":{"id":1},"client":{"id":1},"quantite":2}' http://localhost:8080/api/commandes
6. Vues et templates
6.1. Intégration de Thymeleaf comme moteur de templates
Thymeleaf est un moteur de templates moderne pour Spring Boot, permettant de générer des pages HTML dynamiques. Il est intégré via le starter spring-boot-starter-thymeleaf (déjà dans notre pom.xml) et fonctionne avec des fichiers HTML dans src/main/resources/templates.Configuration existante (rappel) - application.properties :
spring.thymeleaf.cache=false
- Désactive le cache pour voir les modifications en développement sans redémarrer.
6.2. Création de pages dynamiques
Nous allons créer des pages Thymeleaf pour lister et gérer les catégories, en réutilisant le CategorieController existant pour les données REST, mais en passant par des contrôleurs MVC pour les vues.Contrôleur MVC - src/main/java/com/exemple/ventes/controller/CategorieWebController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Categorie;
import com.exemple.ventes.repository.CategorieRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class CategorieWebController {
@Autowired
private CategorieRepository categorieRepository;
@GetMapping("/categories")
public String listerCategories(Model model) {
model.addAttribute("categories", categorieRepository.findAll());
return "categories/liste";
}
}
Vue - src/main/resources/templates/categories/liste.html :
<!DOCTYPE >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Liste des Catégories</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>Liste des Catégories</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Nom</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="categorie : ${categories}">
<td th:text="${categorie.id}"></td>
<td th:text="${categorie.nom}"></td>
<td th:text="${categorie.description}"></td>
</tr>
</tbody>
</table>
<a href="/categories/ajouter">Ajouter une catégorie</a>
</body>
</html>
CSS (optionnel) - src/main/resources/static/css/style.css :
table {
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid black;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
Explication :
- @Controller : Utilisé pour les vues (au lieu de @RestController).
- Model : Transmet les données à la vue.
- th:each : Boucle sur la liste des catégories.
- Retourne "categories/liste" : Résout vers templates/categories/liste.html.
graph TD
A[Pages Dynamiques] --> B(@Controller)
B --> C[Model]
A --> D[Thymeleaf]
D --> E[th:each]
D --> F[th:text]
C --> G[Liste Categories]
6.3. Gestion des formulaires et validation
Nous allons ajouter un formulaire pour créer une catégorie, avec validation des champs.Ajout dans CategorieWebController.java :
package com.exemple.ventes.controller;
import com.exemple.ventes.model.Categorie;
import com.exemple.ventes.repository.CategorieRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class CategorieWebController {
@Autowired
private CategorieRepository categorieRepository;
@GetMapping("/categories")
public String listerCategories(Model model) {
model.addAttribute("categories", categorieRepository.findAll());
return "categories/liste";
}
@GetMapping("/categories/ajouter")
public String afficherFormulaireAjout(Model model) {
model.addAttribute("categorie", new Categorie());
return "categories/ajouter";
}
@PostMapping("/categories/ajouter")
public String ajouterCategorie(@Valid Categorie categorie, BindingResult result, Model model) {
if (result.hasErrors()) {
return "categories/ajouter";
}
categorieRepository.save(categorie);
return "redirect:/categories";
}
}
Mise à jour de Categorie.java avec validation :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Entity
@Data
public class Categorie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Le nom est requis")
private String nom;
private String description;
}
Vue - src/main/resources/templates/categories/ajouter.html :
<!DOCTYPE >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Ajouter une Catégorie</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>Ajouter une Catégorie</h1>
<form th:action="@{/categories/ajouter}" th:object="${categorie}" method="post">
<div>
<label>Nom :</label>
<input type="text" th:field="*{nom}">
<span th:errors="*{nom}" class="error"></span>
</div>
<div>
<label>Description :</label>
<textarea th:field="*{description}"></textarea>
</div>
<button type="submit">Ajouter</button>
</form>
<a href="/categories">Retour à la liste</a>
</body>
<style>
.error { color: red; }
</style>
</html>
Explication :
- @Valid et BindingResult : Valident les champs et gèrent les erreurs.
- th:action : Définit l’URL de soumission.
- th:field : Lie les champs du formulaire à l’objet Categorie.
- th:errors : Affiche les messages d’erreur.
6.4. Comparaison avec JSP et autres moteurs
Explication :
- Thymeleaf : Templates HTML naturels, faciles à éditer, intégration native avec Spring Boot.
- JSP : Plus ancien, nécessite un WAR et un serveur externe, moins flexible.
- Freemarker : Similaire à Thymeleaf, mais syntaxe différente.
Tableau :
Aspect | Thymeleaf | JSP | Freemarker |
---|---|---|---|
Syntaxe | HTML naturel | Tags Java | FTL |
Serveur | Embarqué | Externe | Embarqué |
Facilité | Élevée | Moyenne | Élevée |
Projet CRUD : Gestion des ventes - Progression
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ ├── controller/
│ │ │ │ ├── TestController.java
│ │ │ │ ├── CategorieController.java
│ │ │ │ ├── ProduitController.java
│ │ │ │ ├── ClientController.java
│ │ │ │ ├── CommandeController.java
│ │ │ │ └── CategorieWebController.java
│ │ │ ├── model/
│ │ │ │ ├── Categorie.java
│ │ │ │ ├── Produit.java
│ │ │ │ ├── Client.java
│ │ │ │ └── Commande.java
│ │ │ ├── repository/
│ │ │ │ ├── CategorieRepository.java
│ │ │ │ ├── ProduitRepository.java
│ │ │ │ ├── ClientRepository.java
│ │ │ │ └── CommandeRepository.java
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ ├── static/
│ │ │ └── css/
│ │ │ └── style.css
│ │ ├── templates/
│ │ │ └── categories/
│ │ │ ├── liste.html
│ │ │ └── ajouter.html
│ │ └── application.properties
├── pom.xml
État actuel :
- Vues Thymeleaf pour Categorie :
- /categories : Liste des catégories.
- /categories/ajouter : Formulaire d’ajout avec validation.
- API REST reste disponible pour toutes les entités.
Test :
- Lancez l’application (mvn spring-boot:run).
- Accédez à http://localhost:8080/categories.
- Ajoutez une catégorie via le formulaire et vérifiez la liste mise à jour.
Prochaines étapes :
- Ajouter la sécurité avec Spring Security (section 7).
7. Sécurité avec Spring Security
7.1. Introduction à Spring Security dans Spring Boot
Spring Security est un module puissant intégré à Spring Boot pour sécuriser les applications. Il offre :- Authentification (vérification d’identité).
- Autorisation (contrôle des accès).
- Protection contre les attaques (ex. CSRF).
Nous allons sécuriser notre application pour exiger une connexion avant d’accéder aux pages et API.
7.2. Configuration de l’authentification (in-memory, JDBC)
Nous commencerons avec une authentification in-memory simple (pour tester), puis passerons à une authentification JDBC basée sur la table Client.Ajout de la dépendance dans pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Configuration in-memory initiale - src/main/java/com/exemple/ventes/config/SecurityConfig.java :
package com.exemple.ventes.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.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**").permitAll() // Accès public au CSS
.anyRequest().authenticated() // Tout le reste nécessite une authentification
)
.formLogin(form -> form
.loginPage("/login") // Page de login personnalisée
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
var user = User.withDefaultPasswordEncoder()
.username("admin")
.password("1234")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
Explication :
- @EnableWebSecurity : Active Spring Security.
- securityFilterChain : Configure les règles d’accès.
- UserDetailsService : Définit un utilisateur en mémoire (admin/1234).
7.3. Sécurisation des endpoints REST et des pages
Nous allons sécuriser les pages Thymeleaf et les API REST, tout en ajoutant une page de login.Page de login - src/main/resources/templates/login.html :
<!DOCTYPE >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Connexion</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>Connexion</h1>
<div th:if="${param.error}" class="error">Nom d'utilisateur ou mot de passe incorrect.</div>
<div th:if="${param.logout}" class="success">Déconnexion réussie.</div>
<form th:action="@{/login}" method="post">
<div>
<label>Nom d'utilisateur :</label>
<input type="text" name="username">
</div>
<div>
<label>Mot de passe :</label>
<input type="password" name="password">
</div>
<button type="submit">Se connecter</button>
</form>
</body>
<style>
.error { color: red; }
.success { color: green; }
</style>
</html>
Mise à jour de CategorieWebController.java : Ajoutez un lien de déconnexion :
@GetMapping("/categories")
public String listerCategories(Model model) {
model.addAttribute("categories", categorieRepository.findAll());
return "categories/liste";
}
Mise à jour de liste.html :
<a href="/logout">Déconnexion</a>
<!-- Ajouter après "Ajouter une catégorie" -->
Sécurisation des API REST : Modifiez SecurityConfig.java pour limiter les API aux utilisateurs authentifiés :
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/login").permitAll()
.requestMatchers("/api/**").authenticated() // Sécurise toutes les API
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
7.4. Gestion des rôles et permissions (JDBC)
Passons à une authentification basée sur la table Client avec email/password comme identifiants.Mise à jour de Client.java : Ajoutez un rôle :
package com.exemple.ventes.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Entity
@Data
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Le nom est requis")
private String nom;
@Email(message = "Email invalide")
private String email;
@NotBlank(message = "Le mot de passe est requis")
private String password;
private String status;
private String role = "USER"; // Par défaut, tous sont USER
}
Service de sécurité - src/main/java/com/exemple/ventes/service/ClientUserDetailsService.java :
package com.exemple.ventes.service;
import com.exemple.ventes.model.Client;
import com.exemple.ventes.repository.ClientRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class ClientUserDetailsService implements UserDetailsService {
@Autowired
private ClientRepository clientRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Client client = clientRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Client non trouvé : " + email));
return User.withUsername(client.getEmail())
.password(client.getPassword()) // Doit être encodé en production
.roles(client.getRole())
.build();
}
}
Ajout dans ClientRepository.java :
import java.util.Optional;
public interface ClientRepository extends JpaRepository<Client, Long> {
Optional<Client> findByEmail(String email);
}
Mise à jour de SecurityConfig.java pour JDBC :
package com.exemple.ventes.config;
import com.exemple.ventes.service.ClientUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private ClientUserDetailsService clientUserDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/login").permitAll()
.requestMatchers("/api/**").authenticated()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
return clientUserDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Mise à jour de ClientController.java pour encoder le mot de passe :
@PostMapping
public ResponseEntity<Client> ajouterClient(@Valid @RequestBody Client client) {
client.setPassword(passwordEncoder.encode(client.getPassword()));
Client savedClient = clientRepository.save(client);
return ResponseEntity.status(201).body(savedClient);
}
@PutMapping("/{id}")
public ResponseEntity<Client> mettreAJourClient(@PathVariable Long id, @Valid @RequestBody Client client) {
if (!clientRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
client.setId(id);
client.setPassword(passwordEncoder.encode(client.getPassword()));
Client updatedClient = clientRepository.save(client);
return ResponseEntity.ok(updatedClient);
}
@Autowired
private PasswordEncoder passwordEncoder; // Ajouter au contrôleur
Explication :
- ClientUserDetailsService : Charge les utilisateurs depuis la table Client.
- BCryptPasswordEncoder : Encode les mots de passe pour la sécurité.
- Les clients doivent maintenant être ajoutés via l’API avec un mot de passe clair, qui sera encodé.
Test :
- Ajoutez un client via l’API : curl -X POST -H "Content-Type: application/json" -d '{"nom":"Bob","email":"bob@example.com","password":"1234","status":"actif"}' http://localhost:8080/api/clients
- Connectez-vous avec bob@example.com / 1234 sur /login.
Projet CRUD : Gestion des ventes - Progression
Structure actuelle :
gestion-ventes/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/ventes/
│ │ │ ├── config/
│ │ │ │ └── SecurityConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── TestController.java
│ │ │ │ ├── CategorieController.java
│ │ │ │ ├── ProduitController.java
│ │ │ │ ├── ClientController.java
│ │ │ │ ├── CommandeController.java
│ │ │ │ └── CategorieWebController.java
│ │ │ ├── model/
│ │ │ │ ├── Categorie.java
│ │ │ │ ├── Produit.java
│ │ │ │ ├── Client.java
│ │ │ │ └── Commande.java
│ │ │ ├── repository/
│ │ │ │ ├── CategorieRepository.java
│ │ │ │ ├── ProduitRepository.java
│ │ │ │ ├── ClientRepository.java
│ │ │ │ └── CommandeRepository.java
│ │ │ ├── service/
│ │ │ │ └── ClientUserDetailsService.java
│ │ │ └── GestionVentesApplication.java
│ │ └── resources/
│ │ ├── static/
│ │ │ └── css/
│ │ │ └── style.css
│ │ ├── templates/
│ │ │ ├── categories/
│ │ │ │ ├── liste.html
│ │ │ │ └── ajouter.html
│ │ │ └── login.html
│ │ └── application.properties
├── pom.xml
État actuel :
- Application sécurisée avec Spring Security :
- Authentification basée sur Client (email/password).
- Pages (/categories) et API (/api/**) protégées.
- Page de login personnalisée.
Test :
- Lancez l’application (mvn spring-boot:run).
- Accédez à http://localhost:8080/categories : redirection vers /login.
- Connectez-vous avec un client existant (ex. bob@example.com / 1234).
Prochaines étapes :
- Ajouter des fonctionnalités avancées (section 8).