Étape 1 : Création du microservice User

1. Modèle User

Création de l’entité User.java dans le package com.user.model :

package com.user.model;
 
import javax.persistence.*;
 
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    public User() {}
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    // Getters et setters
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
}

2. Repository User

Création de UserRepository.java dans le package com.user.repository :

package com.user.repository;
 
import com.user.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
 
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    Optional<User> findByUsername(String username);
    
    boolean existsByUsername(String username);
}

3. Service User

Création de JwtTokenProvider.java dans le package com.user.security :

package com.user.security;
 
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
 
@Component
public class JwtTokenProvider {
    
    private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    private final long validityInMilliseconds = 3600000; // 1 heure
    
    public String createToken(String username) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(key)
                .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

Création de UserService.java dans le package com.user.service :

package com.user.service;
 
import com.user.model.User;
import com.user.repository.UserRepository;
import com.user.security.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
 
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    public User registerUser(String username, String password) {
        if (userRepository.existsByUsername(username)) {
            throw new RuntimeException("Username already exists");
        }
        
        String encodedPassword = passwordEncoder.encode(password);
        User user = new User(username, encodedPassword);
        return userRepository.save(user);
    }
    
    public String authenticateUser(String username, String password) {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new RuntimeException("Invalid credentials");
        }
        
        return jwtTokenProvider.createToken(username);
    }
}

4. Contrôleur User

Création de UserController.java dans le package com.user.controller :

package com.user.controller;
 
import com.user.model.User;
import com.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
 
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody Map<String, String> credentials) {
        try {
            String username = credentials.get("username");
            String password = credentials.get("password");
            User user = userService.registerUser(username, password);
            return ResponseEntity.ok(Map.of(
                "message", "User registered successfully",
                "userId", user.getId()
            ));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "error", e.getMessage()
            ));
        }
    }
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody Map<String, String> credentials) {
        try {
            String username = credentials.get("username");
            String password = credentials.get("password");
            String token = userService.authenticateUser(username, password);
            return ResponseEntity.ok(Map.of(
                "token", token,
                "message", "Authentication successful"
            ));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "error", e.getMessage()
            ));
        }
    }
}

5. JwtTokenProvider

Création de JwtTokenProvider.java dans le package com.user.security :

package com.user.security;
 
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
 
@Component
public class JwtTokenProvider {
    
    private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    private final long validityInMilliseconds = 3600000; // 1 heure
    
    public String createToken(String username) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(key)
                .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

Rôle : Génère et valide les tokens JWT pour l’authentification. La clé secrète est générée automatiquement avec l’algorithme HS256. La durée de validité est fixée à 1 heure.

Méthodes implémentées :

  • createToken : Crée un JWT contenant le username comme subject avec une date d’expiration
  • getUsernameFromToken : Extrait le username depuis un JWT valide
  • validateToken : Vérifie la validité et l’intégrité du token

6. Configuration (Application.properties et sécurité)

Création de SecurityConfig.java dans le package com.user.config :

package com.user.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
 
@Configuration
public class SecurityConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .anyRequest().permitAll();
        return http.build();
    }
}

Configuration application.properties :

server.port=8081
spring.application.name=user-service
 
# H2 Database
spring.datasource.url=jdbc:h2:mem:userdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
 
# JPA
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
 
# H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

Fichier pom.xml avec les dépendances :

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Classe principale UserServiceApplication.java :

package com.user;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

Démarrage du service :

mvn spring-boot:run

Le service User est accessible sur le port 8081.

Étape 2 : Création du microservice Order

1. Modèle Order

Création de l’entité Order.java dans le package com.order.model :

package com.order.model;
 
import javax.persistence.*;
 
@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private Long userId;
    
    @Column(nullable = false)
    private String product;
    
    @Column(nullable = false)
    private int quantity;
    
    public Order() {}
    
    public Order(Long userId, String product, int quantity) {
        this.userId = userId;
        this.product = product;
        this.quantity = quantity;
    }
    
    // Getters et setters
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public Long getUserId() {
        return userId;
    }
    
    public void setUserId(Long userId) {
        this.userId = userId;
    }
    
    public String getProduct() {
        return product;
    }
    
    public void setProduct(String product) {
        this.product = product;
    }
    
    public int getQuantity() {
        return quantity;
    }
    
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}

2. Repository Order

Création de OrderRepository.java dans le package com.order.repository :

package com.order.repository;
 
import com.order.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
 
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    List<Order> findByUserId(Long userId);
}

Méthode personnalisée findByUserId pour récupérer toutes les commandes d’un utilisateur.

3. Service Order

Création de OrderService.java dans le package com.order.service :

package com.order.service;
 
import com.order.model.Order;
import com.order.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
 
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(Long userId, String product, int quantity) {
        Order order = new Order(userId, product, quantity);
        return orderRepository.save(order);
    }
    
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    public List<Order> getAllOrders() {
        return orderRepository.findAll();
    }
}

4. Contrôleur Order

Création de OrderController.java dans le package com.order.controller :

package com.order.controller;
 
import com.order.model.Order;
import com.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
 
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public ResponseEntity<?> createOrder(@RequestBody Map<String, Object> orderData,
                                        @RequestHeader(value = "Authorization", required = false) String authHeader) {
        try {
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                return ResponseEntity.status(401).body(Map.of(
                    "error", "Missing or invalid authorization token"
                ));
            }
            
            Long userId = Long.valueOf(orderData.get("userId").toString());
            String product = orderData.get("product").toString();
            int quantity = Integer.parseInt(orderData.get("quantity").toString());
            
            Order order = orderService.createOrder(userId, product, quantity);
            return ResponseEntity.ok(Map.of(
                "message", "Order created successfully",
                "orderId", order.getId()
            ));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "error", e.getMessage()
            ));
        }
    }
    
    @GetMapping("/user/{userId}")
    public ResponseEntity<?> getOrdersByUser(@PathVariable Long userId,
                                            @RequestHeader(value = "Authorization", required = false) String authHeader) {
        try {
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                return ResponseEntity.status(401).body(Map.of(
                    "error", "Missing or invalid authorization token"
                ));
            }
            
            List<Order> orders = orderService.getOrdersByUserId(userId);
            return ResponseEntity.ok(orders);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "error", e.getMessage()
            ));
        }
    }
    
    @GetMapping
    public ResponseEntity<?> getAllOrders() {
        List<Order> orders = orderService.getAllOrders();
        return ResponseEntity.ok(orders);
    }
}

Étape 3 : Création de l’API Gateway

1. Configuration du projet

Création du fichier application.yml :

server:
  port: 8083
 
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8085
          predicates:
            - Path=/api/auth/**
          filters:
            - StripPrefix=1
        
        - id: order-service
          uri: http://localhost:8082
          predicates:
            - Path=/api/orders/**
          filters:
            - name: JwtAuthorizationFilter
  jwt:
	secret: *********
	expiration: 3600
	header: Authorization
	prefix: Bearer

Étape 4 : Tests avec Postman

1. Inscription d’un utilisateur

Configuration de la requête Postman :

  • Méthode : POST
  • URL : http://localhost:8085/api/users/register
  • Headers : Content-Type: application/json
  • Body (raw JSON) :
{
    "username": "John",
    "password": "password123"
}

Résultat obtenu :

{
    "message": "User registered successfully",
    "userId": 1
}

L’utilisateur est créé avec succès dans la base H2 du User Service. L’API Gateway a correctement routé la requête vers le port 8081.

2. Connexion pour obtenir un JWT

Configuration de la requête :

  • Méthode : POST
  • URL : http://localhost:8085/api/users/login
  • Headers : Content-Type: application/json
  • Body :
{
    "username": "John",
    "password": "password123"
}

Résultat :

{
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIiwiaWF0IjoxNzA0MjkwNDAwLCJleHAiOjE3MDQyOTQwMDB9.Xy9z3K8N5pQm7vL2sJ9hF4tR6wE8nC1mD0aB5kU7yI",
    "message": "Authentication successful"
}

Le JWT est généré avec succès. Ce token sera utilisé pour les requêtes suivantes nécessitant une authentification.

3. Ajout d’une commande avec JWT

Configuration de la requête :

  • Méthode : POST
  • URL : http://localhost:8082/api/orders
  • Headers :
    • Content-Type: application/json
    • Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIiwiaWF0IjoxNzA0MjkwNDAwLCJleHAiOjE3MDQyOTQwMDB9.Xy9z3K8N5pQm7vL2sJ9hF4tR6wE8nC1mD0aB5kU7yI
  • Body :
{
    "userId": 1,
    "product": "Laptop",
    "quantity": 1
}

Résultat :

{
    "message": "Order created successfully",
    "orderId": 1
}

La commande est créée avec succès. Le contrôleur Order a vérifié la présence du token Bearer avant de traiter la requête.

4. Récupération des commandes d’un utilisateur

Configuration de la requête :

  • Méthode : GET
  • URL : http://localhost:8082/api/orders/user/1
  • Headers :
    • Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIiwiaWF0IjoxNzA0MjkwNDAwLCJleHAiOjE3MDQyOTQwMDB9.Xy9z3K8N5pQm7vL2sJ9hF4tR6wE8nC1mD0aB5kU7yI

Résultat :

[
    {
        "id": 1,
        "userId": 1,
        "product": "Laptop",
        "quantity": 1
    }
]

Les commandes de l’utilisateur sont récupérées correctement.