Aller au contenu

Backend Spring Boot — Structure & Développement

Structure du projet

perfshop-app/src/main/java/com/perfshop/
├── chaos/
│   ├── ChaosService.java              # État des anomalies (AtomicInteger)
│   ├── ChaosInterceptor.java          # Intercepteur HTTP — Thread Pool, Slow Query, Network
│   ├── ChaosScriptingService.java     # Chaos Scripting — tokens, validation, rotation (volatile)
│   ├── BusinessChaosService.java      # Chaos Métier — 11 anomalies A1-A11, activity log
│   ├── CpuChaosScheduler.java         # Boucle SHA-256 fixedRate=100ms
│   ├── MemoryLeakSimulator.java       # Accumulation objets + auto-nettoyage à intensité 0
│   ├── DbPoolChaosScheduler.java      # Saturation HikariCP (synchronizedList)
│   └── ContainerCpuMetrics.java       # Métriques CPU container
├── controller/
│   ├── ProductController.java
│   ├── CartController.java            # DTO manuel (fix ByteBuddyInterceptor)
│   ├── OrderController.java           # Deadlock chaos + A10 total historique
│   ├── AuthController.java            # A11 grace period logout
│   ├── CheckoutController.java        # A6 promo invalide acceptée, A7 délai livraison
│   ├── UserController.java            # A9 injection logs, injection SQL postal/country
│   ├── AdminController.java           # Auth double (session + X-Admin-Token), CRUD, images
│   ├── BusinessChaosController.java   # Admin + endpoints publics chaos métier
│   ├── ChaosScriptingController.java  # Admin + public/status + public/logs
│   └── FrontendChaosController.java   # État chaos frontend (poll navigateur)
├── service/
│   ├── ProductService.java            # applySlowQueryChaos() unifié
│   ├── OrderService.java              # A1,A2,A3,A4,A5,A7,A8 + deadlock progressif
│   ├── CartService.java
│   └── AuthService.java
├── repository/
└── entity/
    ├── Product.java                   # champ stock : `stock` (pas stockQuantity)
    ├── User.java                      # profil étendu (V26), civility (V27)
    ├── Order.java                     # OrderStatus, cardLast4, cvvVerified
    ├── OrderItem.java
    └── CartItem.java

API Chaos Backend

Lire l'état actuel

GET /api/admin/chaos/status
{
  "cpu": 0,
  "cpuRatio": 1,
  "memory": 0,
  "threadPool": 0,
  "dbPool": 0,
  "slowQuery": 0,
  "deadlock": 0,
  "network": 0
}

Clé network (pas networkTimeout)

La clé retournée par /api/admin/chaos/status est network. Le chaos-admin utilise cette clé pour afficher l'état en temps réel.

Modifier une anomalie

POST /api/admin/chaos/cpu          { "intensity": 80, "ratio": 3 }
POST /api/admin/chaos/memory       { "intensity": 50 }
POST /api/admin/chaos/thread-pool  { "intensity": 60 }
POST /api/admin/chaos/db-pool      { "intensity": 40 }
POST /api/admin/chaos/slow-queries { "intensity": 70 }
POST /api/admin/chaos/deadlock     { "intensity": 80 }
POST /api/admin/chaos/network      { "intensity": 50 }

Reset global

POST /api/admin/chaos/reset

API Chaos Métier

Contrôle admin

POST /api/admin/chaos/business
{ "level": 1 }    # 0=off, 1=Junior, 2=Confirmé, 3=Expert

Endpoints publics (sans authentification — pour le monitoring)

GET /api/chaos/public/business/status
GET /api/chaos/public/business/logs
GET /api/chaos/public/business/anomalies?level=N
GET /api/chaos/public/status          # backend + frontend + business combinés

API Chaos Scripting

Contrôle admin

POST /api/admin/chaos/scripting/level
{ "level": 2 }    # 0=off, 1=Junior, 2=Confirmé, 3=Expert, 4=Maestro

Endpoints publics (sans authentification — pour le monitoring)

GET /api/admin/chaos/scripting/public/status
GET /api/admin/chaos/scripting/public/logs

Ne pas appeler depuis le monitoring

/api/admin/chaos/status, /api/admin/chaos/scripting/status et /api/admin/chaos/scripting/logs requièrent auth admin → retournent 401 en boucle si appelés sans cookie de session ou X-Admin-Token. Utiliser uniquement les endpoints /public/ depuis le dashboard monitoring.


API Admin

Authentification double mécanisme

  1. Cookie de session (admin_logged_in = true) — utilisé par l'interface chaos-admin
  2. Header X-Admin-Token — UUID stocké dans VALID_ADMIN_TOKENS (ConcurrentHashMap statique dans AdminController)
POST /api/admin/login
{ "email": "admin@perfshop.fr", "password": "perfshop" }
# Réponse : cookie admin_logged_in + header X-Admin-Token

La méthode statique AdminController.isValidAdminToken(token) est utilisée par ChaosController, ChaosScriptingController et BusinessChaosController pour valider les requêtes cross-origin.

Reset au redémarrage

Les tokens admin sont stockés en mémoire statique — ils sont perdus au redémarrage du backend. C'est intentionnel : le chaos-admin doit se reconnecter après un redémarrage.


ChaosInterceptor — point d'entrée des anomalies HTTP

ChaosInterceptor est enregistré dans WebConfig et s'applique à toutes les requêtes HTTP sauf /actuator et /api/admin/chaos.

Il gère trois anomalies dans l'ordre :

1. applyThreadPoolChaos()   → sleep proportionnel à l'intensité
2. applySlowQueryChaos()    → sleep (hors /api/products, géré par ProductService)
3. applyNetworkChaos()      → délai + 503 aléatoire

Endpoints couverts par Network : /api/orders, /api/auth, /api/products, /api/cart.


BusinessChaosService — 11 anomalies

Activity Log

Chaque anomalie déclenchée produit une entrée dans un ring buffer de 200 entrées max avec les champs : ts, time, type, severity (INFO/WARN/ERROR), user, detail, technical, level.

Points d'injection par anomalie

Anomalie Point d'injection Niveau
A1 TVA 19.6% OrderService.createOrderFromItems() 1
A2 Arrondi floor OrderService.createOrderFromItems() 1
A3 Stock non décrémenté OrderService.createOrderFromItems() 1
A4 Email sans frais de port OrderService.createOrderFromItems() 2
A5 Double commande OrderService.createOrderFromItems() 2
A6 Promo invalide acceptée CheckoutController.applyPromo() 2
A7 Délai calendaire OrderService + CheckoutController 2
A8 Race condition stock OrderService.createOrderFromItems() 3
A9 Injection logs UserController.updateProfile() + CheckoutController.applyPromo() 3
A10 Total historique faux OrderController.getUserOrders() 3
A11 Token non invalidé 30s AuthController.logout() 3

Injection SQL chaos (niveau 2 et 3)

En plus des anomalies métier, des injections SQL sont simulées sur le code postal et le pays :

  • N2 : postal_code"999999" (6 chars > VARCHAR(5)) → DataTruncation
  • N3 : postal_code"9999999999", country"FRANCE" (6 chars > VARCHAR(2))

Codes promos valides (tous niveaux)

Code Réduction
PROMO10 -10%
PROMO20 -20%
WELCOME5 -5%

ChaosScriptingService — architecture tokens

TokenBundle — thread safety

Les champs csrfToken, stepToken, signature, hmacKey sont déclarés volatile pour éviter les race conditions si plusieurs requêtes arrivent simultanément sur le même bundle.

Niveaux de protection

Niveau Headers requis Expiration Particularité
0 Pass-through
1 Junior X-Session-Token + X-Request-ID Aucune Tokens persistants
2 Confirmé + X-Action-Token 30 secondes Expiration stricte
3 Expert + X-CSRF-Token + X-Step-Token + X-Signature 15 secondes HMAC-SHA256 statique
4 Maestro Idem 15 secondes HMAC-SHA256 avec clé dérivée du sessionToken

Séquence Expert/Maestro

step1 → POST /api/cart/add
step2 → POST /api/checkout/address  (ou équivalent)
step3 → POST /api/checkout/shipping
step4 → POST /api/orders

Clé HMAC Expert : perfshop-chaos-expert-secret-2025 Payload : sessionToken:csrfToken:stepToken

validate() avec rotate=false

// Sans rotation — cart/add peut être appelé plusieurs fois sans casser la séquence
validate(session, headers, "step1", false)

Permet d'ajouter plusieurs articles au panier sans consommer un step à chaque fois.


CartController — fix ByteBuddyInterceptor

Le CartController retourne des DTOs manuels (Map<String, Object>) au lieu des entités CartItem directement.

Cause du bug résolu : CartItem.user est FetchType.LAZY → Hibernate crée un proxy ByteBuddy. Quand Jackson sérialise le CartItem retourné, il tente de sérialiser le proxy non initialisé → 500 ByteBuddyInterceptor.

// DTO manuel — ne touche jamais à cartItem.getUser()
Map<String, Object> dto = new HashMap<>();
dto.put("id",          cartItem.getId());
dto.put("productId",   cartItem.getProduct().getId());
dto.put("quantity",    cartItem.getQuantity());
dto.put("price",       cartItem.getPrice());
dto.put("addedAt",     cartItem.getAddedAt());

OrderController — breaking change

Depuis l'ajout de l'anomalie A10, GET /api/orders retourne un objet enveloppé :

{
  "orders": [ ... ],
  "historyTotal": 299.97
}

Le frontend (api.js + MyOrders.jsx) déstructure ce format. Attention si un script de test appelle directement cet endpoint — il ne retourne plus un tableau brut.


CVV — validation et destruction

Le CVV est validé dans CheckoutController.submitPayment() puis jamais stocké ni logué. Seul un flag cvvVerified = true est transmis à OrderService via session.


Formules de délai — référence

Anomalie 25% 50% 75% 100%
Thread Pool 750ms 1500ms 3000ms 5000ms
Slow Query 375ms 2000ms 4000ms 6000ms
Network (intercepteur) 500ms 1500ms 3000ms 6000ms
Network (paiement) 500ms 1500ms 2500ms 4000ms
Deadlock (attente max) 1000ms 2000ms 3000ms 5000ms

Métriques Prometheus clés

Métrique Type Anomalie associée
docker_container_cpu_percent{container="perfshop-app"} gauge CPU Burn
jvm_memory_used_bytes{area="heap"} gauge Memory Leak
tomcat_threads_busy_threads gauge Thread Pool
hikaricp_connections_active gauge DB Pool
hikaricp_connections_pending gauge DB Pool
http_server_requests_seconds_bucket histogram Slow Query, Network, Thread Pool
jvm_gc_pause_seconds_sum counter Memory Leak
jvm_threads_states_threads{state="blocked"} gauge Deadlock
http_server_requests_seconds_count{status=~"5.."} counter Deadlock, Network 503

Migrations Flyway — état

Migration Contenu
V1–V10 Schéma initial, utilisateurs, produits de base
V11 ALTER TABLE prix DECIMAL(18,2)
V12–V13 +190 produits informatique
V14–V15 +155 avions
V16–V17 +150 voitures
V18–V19 +230 entreprises
V20 Migration URLs images /images/products/
V21 +170 hélicoptères
V22–V25 Réservées
V26 Profil utilisateur étendu (adresse, téléphone)
V27 Civilité utilisateur
V28 Code postal VARCHAR réduit (contrainte intentionnelle pour A9 SQL)

Règle d'or Flyway

Ne jamais modifier un fichier de migration déjà appliqué. Si inévitable, corriger le checksum directement en DB : UPDATE flyway_schema_history SET checksum=... WHERE version='XX'.