Skip to content

taucle/orc-ecommerce-platform

Repository files navigation

Sécurisation JWT & Authentification - Guide Complet

Aperçu d'Architecture

L'authentification de la plateforme e-commerce utilise JWT (JSON Web Tokens) signés avec un chiffrement asymétrique RSA-2048. Voici le flux de sécurité global :

┌──────────────┐                    ┌─────────────────────┐                   ┌──────────────┐
│   Client     │                    │ Membership Service  │                   │              │
│              │                    │  (ms-membership)    │                   │  BD: Users   │
└──────────────┘                    └─────────────────────┘                   └──────────────┘
       │                                      │                                     │
       │ POST /api/v1/auth/login              │                                     │
       │ {email, password}                    │                                     │
       ├────────────────────────────────────>│                                     │
       │                                      │ Chercher utilisateur par email      │
       │                                      ├────────────────────────────────────>│
       │                                      │                                     │
       │                                      │<─────────────────────────────────────┤
       │                                      │ User{id, email, active}             │
       │                                      │                                     │
       │                                      │ Signer JWT avec clé privée RSA      │
       │                                      │ Payload: userId, email, roles, exp  │
       │                                      │                                     │
       │<──────────────────────────────────────┤                                     │
       │ {token, expiresIn: 3600}             │                                     │
       │                                      │                                     │

┌──────────────┐                    ┌─────────────────────┐
│   Client     │                    │ Product Service     │
│              │                    │  (ms-product)       │
└──────────────┘                    └─────────────────────┘
       │                                     │
       │ GET /api/v1/products                │
       │ Authorization: Bearer <JWT>         │
       ├────────────────────────────────────>│
       │                                     │ Extraire token du header
       │                                     │ Valider signature avec clé publique RSA
       │                                     │ Vérifier expiration (exp claim)
       │                                     │ Extraire userId, roles
       │                                     │
       │<──────────────────────────────────────┤
       │ [Produits]                          │
       │                                     │

1. Génération & Gestion des Clés RSA

Pourquoi RSA asymétrique ?

  • Clé privée : Gardée secrète par ms-membership. Utilisée pour signer les tokens.
  • Clé publique : Partagée avec tous les services. Utilisée pour vérifier la signature des tokens.
  • Avantage : Pas besoin de partager la clé privée aux autres services.

Génération des Clés (Sur votre machine)

Vous devez générer une paire de clés RSA 2048 bits.

Étape 1 : Installer OpenSSL

Étape 2 : Générer la Clé Privée

Ouvrez un terminal/PowerShell et exécutez :

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

Exemple de sortie :

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCB...
... (clé encodée en Base64) ...
-----END PRIVATE KEY-----

Étape 3 : Extraire la Clé Publique

openssl rsa -pubout -in private_key.pem -out public_key.pem

Exemple de sortie :

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BA...
... (clé encodée en Base64) ...
-----END PUBLIC KEY-----

Étape 4 : Placer les Clés dans les Services

Pour ms-membership (Signature) :

ms-membership/
├── src/
│   └── main/
│       └── resources/
│           └── keys/
│               ├── private_key.pem          ← Garder SECRET !
│               ├── public_key.pem           ← Optionnel (pour info)
│               ├── .gitignore               ← ignore private_key.pem
│               └── README_KEYS.md

Action :

  1. Copiez le fichier private_key.pem générés dans ms-membership/src/main/resources/keys/
  2. NE PAS committer cette clé à Git — elle est déjà ignorée via .gitignore

Note : Le fichier private_key.pem n'est pas stocké dans le dépôt par sécurité. Pour les tests locaux générez la paire RSA et placez private_key.pem dans ms-membership/src/main/resources/keys/ puis copiez public_key.pem vers ms-product et ms-order.

Pour ms-product et ms-order (Validation) :

ms-product/
├── src/
│   └── main/
│       └── resources/
│           └── keys/
│               ├── public_key.pem           ← Copiez depuis ms-membership
│               └── README_KEYS.md

ms-order/
├── src/
│   └── main/
│       └── resources/
│           └── keys/
│               ├── public_key.pem           ← Copiez depuis ms-membership
│               └── README_KEYS.md

Action :

  1. Copiez le fichier public_key.pem dans ms-product/src/main/resources/keys/
  2. Copiez le fichier public_key.pem dans ms-order/src/main/resources/keys/
  3. Vous pouvez committer la clé publique à Git (elle est non-sensible)

2. Architecture JWT

Anatomy d'un JWT (RS256)

Un JWT contient 3 parties séparées par des points :

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidXNlcklkIjoxLCJlbWFpbCI6ImFsaWNlQGV4YW1wbGUuY29tIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImV4cCI6MTY4NzcyNzIwMCwiaWF0IjoxNjg3NzIzNjAwfQ.signature...
│                                                                      │                                                          │
└─────────────────────────────────────────────────────────────────────┘────────────────────────────────────────────────────────┘
                                   HEADER.PAYLOAD                                                  SIGNATURE

Header (base64url decoded)

{
  "alg": "RS256",
  "typ": "JWT"
}
  • alg : Algorithme de signature (RS256 = RSA avec SHA256)
  • typ : Type de token

Payload (base64url decoded)

{
  "sub": "1",
  "userId": 1,
  "email": "alice@example.com",
  "roles": ["ROLE_USER"],
  "iat": 1687723600,
  "exp": 1687727200
}
  • sub : Subject (identité utilisateur)
  • userId : ID utilisateur (custom claim)
  • email : Email utilisateur (custom claim)
  • roles : Rôles utilisateur (custom claim, liste)
  • iat : Issued At (timestamp Unix création)
  • exp : Expiration (timestamp Unix, 1 heure après iat)

Signature (base64url)

HMACSHA256(
  base64url(header) + "." + base64url(payload),
  privateKey
)

Générée avec la clé privée RSA du Membership service. Elle garantit que le token n'a pas été modifié.

Durée d'Expiration

  • Expiration : 3600 secondes (1 heure)
  • Les services vérifient exp et rejettent les tokens expirés avec un 403 Forbidden

3. Flux d'Authentification Détaillé

Phase 1 : Login (Membership Service)

Endpoint : POST /api/v1/auth/login

Requête :

{
  "email": "alice.durand@example.com",
  "password": "password123"
}

Réponse (200 OK) :

{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Erreurs :

  • 400 Bad Request : Données manquantes ou invalides
  • 401 Unauthorized : Email/password invalide
  • 403 Forbidden : Utilisateur désactivé

Code : AuthController.java

Phase 2 : Accès aux Ressources Protégées

Endpoint Example : GET /api/v1/products

Requête :

curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
     http://localhost:8082/api/v1/products

Validations dans JwtAuthFilter :

  1. Extraire le token du header Authorization: Bearer <token>
  2. Parser le JWT (extraire header, payload, signature)
  3. Vérifier la signature avec la clé publique RSA
  4. Vérifier l'expiration (claim exp)
  5. Extraire les claims (userId, email, roles)
  6. Créer un authentication Spring Security avec ces claims
  7. Passer au prochain filtre (chaîne de sécurité)

Réponses :

  • 200 OK : Token valide, accès accordé
  • 401 Unauthorized : Token manquant, invalide ou signature échouée
  • 403 Forbidden : Token expiré

Code :


4. Gestion des Erreurs de Sécurité

Codes HTTP Utilisés

Code Sens Exemple
401 Unauthorized Token manquant, invalide, ou signature incorrecte Pas de header Authorization, clé publique ne correspond pas à la signature
403 Forbidden Token expiré exp claim antérieur à la date actuelle

Exemples de Réponses d'Erreur

Token manquant

curl http://localhost:8082/api/v1/products

Response: 401 Unauthorized

Missing or invalid Authorization header

Token expiré

curl -H "Authorization: Bearer <expired_token>" \
     http://localhost:8082/api/v1/products

Response: 403 Forbidden

Token expired

Signature invalide

curl -H "Authorization: Bearer <token_with_modified_payload>" \
     http://localhost:8082/api/v1/products

Response: 401 Unauthorized

Invalid token signature

5. Configuration Spring Security

SecurityConfig (ms-product & ms-order)

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        JwtAuthFilter jwtFilter = new JwtAuthFilter();

        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/actuator/health").permitAll()  // Health check sans auth
                    .anyRequest().authenticated()                      // Tous les autres endpoints nécessitent un token
            )
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }
}

Points clés :

  • .csrf().disable() : Les JWTs ne sont pas vulnérables aux CSRF (tokens stateless)
  • STATELESS : Pas de sessions ; chaque requête contient son token
  • /actuator/health : Autorisé sans token (pour les health checks des conteneurs Docker)
  • Tous les autres endpoints : Require JWT valide
  • JwtAuthFilter : Injecté avant le filtre d'authentification standard

6. Communication Inter-Services Sécurisée

Quand le service Order appelle le service Product ou User :

// Exemple : Order Service appelle Product Service pour vérifier un produit
@Service
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;

    public void createOrder(OrderRequest request, String jwtToken) {
        // Propager le JWT dans le header
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + jwtToken);

        HttpEntity<Void> entity = new HttpEntity<>(headers);
        ResponseEntity<ProductDTO> response = restTemplate.exchange(
            "http://localhost:8082/api/v1/products/" + request.getProductId(),
            HttpMethod.GET,
            entity,
            ProductDTO.class
        );
        
        // Gérer les erreurs
        if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
            throw new SecurityException("Token invalide au service Product");
        }
        if (response.getStatusCode() == HttpStatus.FORBIDDEN) {
            throw new SecurityException("Token expiré au service Product");
        }
    }
}

7. Diagramme de Séquence : Authentification & Autorisation

┌──────────┐                  ┌─────────────┐             ┌────────────────┐
│  Client  │                  │ Membership  │             │ Product Service│
└──────────┘                  └─────────────┘             └────────────────┘
     │                             │                            │
     │ 1. POST /auth/login         │                            │
     │ (email, password)           │                            │
     ├────────────────────────────>│                            │
     │                             │                            │
     │                    2. Query DB (User)                    │
     │                    Vérifier active=true                  │
     │                             │                            │
     │ 3. Signer JWT               │                            │
     │    RS256(header.payload)    │                            │
     │<──────────────────────────────                           │
     │ {token, expiresIn}          │                            │
     │                             │                            │
     │ 4. GET /products            │                            │
     │ Authorization: Bearer TOKEN │                            │
     ├─────────────────────────────────────────────────────────>│
     │                             │                            │
     │                             │     5. JwtAuthFilter       │
     │                             │        - Extraire token    │
     │                             │        - Parser JWT        │
     │                             │        - Verify RSA sign   │
     │                             │        - Check expiration  │
     │                             │                            │
     │                             │<──────────────────────────┐│
     │                             │  Si valide: authenticate   │
     │                             │  Si invalide/expiré: 401/403
     │                             │                            │
     │<─────────────────────────────────────────────────────────┤
     │                             │     [Products]             │
     │                             │                            │

8. Configuration & Deployment

application.yml (ms-membership)

server:
  port: 8081
  servlet:
    context-path: /

spring:
  application:
    name: ms-membership
  jpa:
    hibernate:
      ddl-auto: update
  h2:
    console:
      enabled: true

jwt:
  secret: unused-with-asymmetric-keys
  expiration: 3600000  # 1 hour in ms

application.yml (ms-product)

server:
  port: 8082
  servlet:
    context-path: /

spring:
  application:
    name: ms-product
  security:
    filter:
      order: 5

jwt:
  public-key-path: classpath:keys/public_key.pem

application.yml (ms-order)

server:
  port: 8083
  servlet:
    context-path: /

spring:
  application:
    name: ms-order
  security:
    filter:
      order: 5

jwt:
  public-key-path: classpath:keys/public_key.pem

9. Tests & Postman

Scénario de Test 1 : Login & Get Token

POST http://localhost:8081/api/v1/auth/login
Content-Type: application/json

{
  "email": "alice.durand@example.com",
  "password": "password123"
}

Réponse (200 OK) :

{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}

Scénario de Test 2 : Access Protected Resource avec Token Valide

GET http://localhost:8082/api/v1/products
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Réponse (200 OK) :

[
  { "id": 1, "name": "Laptop", "price": 999.99 },
  ...
]

Scénario de Test 3 : Access Protected Resource SANS Token

GET http://localhost:8082/api/v1/products

Réponse (401 Unauthorized) :

Missing or invalid Authorization header

Scénario de Test 4 : Access avec Token Expiré

Attendre que le token expire (> 3600 sec) ou modifier le exp claim manuellement.

GET http://localhost:8082/api/v1/products
Authorization: Bearer <expired_token>

Réponse (403 Forbidden) :

Token expired

10. Troubleshooting

Erreur : "Unable to load private key"

Cause : Fichier private_key.pem manquant dans ms-membership/src/main/resources/keys/

Solution :

  1. Générez les clés avec OpenSSL (voir section 1)
  2. Placez private_key.pem dans le bon chemin
  3. Restartez ms-membership

Erreur : "Invalid token signature"

Cause : La clé publique dans ms-product/ms-order ne correspond pas à la clé privée de ms-membership

Solution :

  1. Vérifiez que vous avez copié la même public_key.pem depuis ms-membership
  2. Régénérez les clés si nécessaire
  3. Assurez-vous que public_key.pem dans ms-product et ms-order est identique

Erreur : "Token expired" immédiatement

Cause : L'horloge du serveur est incorrecte

Solution :

  1. Synchronisez l'horloge système
  2. Vérifiez que iat et exp sont correctement définis
  3. Pour les tests, augmentez temporairement expiresInSeconds dans JwtService.generateToken()

11. Checklist de Sécurité

  • ✅ Clé privée générée avec RSA 2048 bits
  • ✅ Clé privée stockée dans .gitignore (jamais committer)
  • ✅ Clé publique copiée dans ms-product et ms-order
  • ✅ JwtAuthFilter implémenté dans tous les services consommateurs
  • ✅ SecurityConfig enregistre JwtAuthFilter avant UsernamePasswordAuthenticationFilter
  • ✅ /actuator/health permettra sans authentication (pour Docker health checks)
  • ✅ Tous les autres endpoints nécessitent JWT valide
  • ✅ Tokens expirent après 1 heure
  • ✅ Tests Postman valident les scénarios 401 et 403

About

No description, website, or topics provided.

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages