Sécurité par couche
Comment appliquer la sécurité à chaque couche de l'architecture : frontend, BFF, microservices et base de données, avec des stratégies spécifiques pour chaque niveau.
Le principe de défense en profondeur
La sécurité dans une architecture distribuée ne peut pas dépendre d’un seul point de contrôle. Si un attaquant franchit une barrière, il doit en trouver une autre. Ce principe s’appelle la défense en profondeur : chaque couche met en œuvre ses propres mesures de sécurité, indépendamment de ce que font les autres.
[Frontend] → [API Gateway] → [BFF] → [Microservices] → [Base de données]
Couche 1 Couche 2 Couche 3 Couche 4 Couche 5
Chaque couche a des menaces spécifiques et nécessite donc des protections spécifiques.
Sécurité côté Frontend
Le frontend est la surface d’attaque la plus exposée : n’importe quel utilisateur peut inspecter le code, manipuler les requêtes et modifier le DOM.
Ce qu’il faut protéger
- Entrées de l’utilisateur : toute donnée saisie par l’utilisateur est potentiellement malveillante
- Jetons de session : si un attaquant obtient le jeton, il peut usurper l’identité de l’utilisateur
- Données sensibles : ne jamais stocker de secrets, de clés API ou de données confidentielles dans le frontend
Stratégies clés
Assainissement des entrées
Ne faites jamais confiance aux données de l’utilisateur. Validez et assainissez côté frontend comme première ligne de défense, mais validez toujours aussi côté backend.
// ❌ Dangereux : insérer du HTML de l'utilisateur directement
element.innerHTML = userInput;
// ✅ Sûr : utiliser textContent ou assainir
element.textContent = userInput;
// Ou utiliser une bibliothèque d'assainissement comme DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);
Stockage sécurisé des jetons
// ❌ Vulnérable au XSS : localStorage est accessible depuis n'importe quel script
localStorage.setItem('token', jwt);
// ✅ Mieux : cookies httpOnly (configurées côté serveur)
// Le frontend ne peut pas accéder au cookie, mais il est envoyé automatiquement
Set-Cookie: token=jwt; HttpOnly; Secure; SameSite=Strict
Content Security Policy (CSP)
Configurez des en-têtes CSP pour limiter les ressources que la page peut charger :
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.ejemplo.com;
Sécurité dans l’API Gateway
L’API Gateway est le point d’entrée unique du système. C’est l’endroit idéal pour mettre en œuvre des contrôles de sécurité transversaux.
Responsabilités de sécurité
- Terminaison TLS : toutes les connexions externes doivent être en HTTPS
- Rate limiting : limiter le nombre de requêtes par IP ou par utilisateur
- Validation des jetons : vérifier que le JWT est valide avant de router
- Filtrage des en-têtes : supprimer les en-têtes internes qui ne doivent pas sortir vers l’extérieur
- Journalisation des accès : enregistrer chaque requête à des fins d’audit
Rate limiting
# Exemple de configuration de rate limiting
rate_limit:
global:
requests_per_second: 100
per_ip:
requests_per_minute: 60
per_user:
requests_per_minute: 120
endpoints:
/auth/login:
requests_per_minute: 5 # Plus restrictif pour la connexion
Validation des jetons au niveau du Gateway
Requête entrante
→ A-t-elle un en-tête Authorization ?
→ Non → Est-ce une route publique ? → Oui → Autoriser
→ Non → 401 Unauthorized
→ Oui → Le jeton est-il valide (signature, expiration) ?
→ Non → 401 Unauthorized
→ Oui → Extraire les claims, les joindre à la requête, router
Sécurité dans le BFF
Le BFF (Backend for Frontend) agit comme intermédiaire entre le frontend et les microservices. Il a des responsabilités de sécurité spécifiques.
Responsabilités
- Transformation sécurisée des données : ne jamais exposer de données internes au frontend
- Validation des entrées : deuxième ligne de validation après le frontend
- Orchestration sécurisée : lors de la combinaison de données provenant de plusieurs services, respecter les permissions de l’utilisateur
- Gestion des erreurs : ne pas divulguer d’informations internes dans les messages d’erreur
Exemple : filtrage des données sensibles
// Réponse du microservice (données internes)
const userFromService = {
id: "user-123",
email: "maria@ejemplo.com",
passwordHash: "$2b$10$...", // Ne jamais exposer !
internalId: "db-row-456", // Donnée interne !
role: "admin",
lastLogin: "2024-01-15T10:30:00Z"
};
// Réponse du BFF au frontend (données filtrées)
const userForFrontend = {
id: userFromService.id,
email: userFromService.email,
role: userFromService.role,
lastLogin: userFromService.lastLogin
};
Gestion sécurisée des erreurs
// ❌ Expose des informations internes
res.status(500).json({
error: "Connection refused to postgres://db-host:5432/users"
});
// ✅ Erreur générique pour le frontend
res.status(500).json({
error: "Error interno del servidor. Intente nuevamente."
});
// Le détail est enregistré dans les logs internes
Sécurité dans les Microservices
Chaque microservice doit supposer que les requêtes qu’il reçoit peuvent être malveillantes, même si elles proviennent d’autres services internes.
Zero Trust entre services
Le modèle Zero Trust établit qu’aucun service ne fait automatiquement confiance à un autre, même au sein du même réseau :
- Chaque service vérifie l’identité de l’appelant
- Chaque service valide les permissions pour l’opération demandée
- La communication entre services est chiffrée (mTLS)
Validation des entrées dans chaque service
// Chaque microservice valide ses propres entrées
function createOrder(data) {
// Valider la structure
if (!data.userId || !data.items || data.items.length === 0) {
throw new ValidationError("Datos de orden inválidos");
}
// Valider les types et les plages
if (typeof data.userId !== 'string' || data.userId.length > 50) {
throw new ValidationError("userId inválido");
}
for (const item of data.items) {
if (item.quantity < 1 || item.quantity > 1000) {
throw new ValidationError("Cantidad fuera de rango");
}
}
// Traiter uniquement après validation
return orderRepository.create(data);
}
Principe du moindre privilège
Chaque microservice ne doit avoir accès qu’aux ressources dont il a besoin :
- Le service des commandes n’a pas besoin d’accéder à la table des utilisateurs
- Le service de notifications n’a pas besoin de pouvoir modifier les commandes
- Les identifiants de base de données sont uniques à chaque service
Sécurité dans la Base de Données
La base de données est la dernière ligne de défense. Si un attaquant parvient jusqu’ici, les données doivent être protégées.
Stratégies clés
Chiffrement au repos
Les données sensibles doivent être chiffrées sur le disque :
-- Exemple : colonnes chiffrées
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- Hash, pas de chiffrement réversible
ssn_encrypted BYTEA, -- Donnée sensible chiffrée avec AES-256
created_at TIMESTAMP DEFAULT NOW()
);
Accès avec le moindre privilège
-- Chaque service dispose de son propre utilisateur de BD avec des permissions limitées
-- Service des commandes : accès uniquement aux tables de commandes
GRANT SELECT, INSERT, UPDATE ON orders TO orders_service;
GRANT SELECT ON products TO orders_service;
-- N'a pas accès à la table des utilisateurs
-- Service des utilisateurs : accès uniquement aux tables d'utilisateurs
GRANT SELECT, INSERT, UPDATE ON users TO users_service;
-- N'a pas accès à la table des commandes
Prévention de l’injection SQL
// ❌ Vulnérable à l'injection SQL
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ Requête paramétrée
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
Audit des accès
Enregistrez qui accède à quelles données et quand :
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
table_name VARCHAR(100),
operation VARCHAR(10), -- SELECT, INSERT, UPDATE, DELETE
user_id VARCHAR(100),
service_name VARCHAR(100),
timestamp TIMESTAMP DEFAULT NOW(),
details JSONB
);
Résumé
La sécurité par couches garantit qu’il n’existe pas de point de défaillance unique. Chaque couche — frontend, API Gateway, BFF, microservices et base de données — met en œuvre ses propres défenses. Le frontend assainit les entrées et protège les jetons, le Gateway contrôle l’accès et limite le trafic, le BFF filtre les données sensibles, les microservices valident tout et appliquent le moindre privilège, et la base de données chiffre et audite. Ensemble, ces couches créent un système où compromettre l’une ne signifie pas compromettre toutes les autres.