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¶
{
"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¶
API Chaos Métier¶
Contrôle admin¶
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)¶
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¶
- Cookie de session (
admin_logged_in = true) — utilisé par l'interface chaos-admin - Header
X-Admin-Token— UUID stocké dansVALID_ADMIN_TOKENS(ConcurrentHashMap statique dansAdminController)
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é :
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'.