Skip to main content

Overview

The API Gateway serves as the single entry point for all client requests to the Sistema de Ventas microservices architecture. Built with Spring Cloud Gateway, it provides intelligent routing, load balancing, authentication, and CORS configuration.

Key Features

  • Unified Entry Point: Single access point for all microservices
  • Service Discovery: Integration with Eureka for dynamic routing
  • Authentication Filter: JWT token validation for protected routes
  • CORS Configuration: Cross-origin resource sharing for frontend integration
  • Load Balancing: Automatic distribution across service instances

Architecture

Configuration

Server Setup

The gateway runs on port 8085 and connects to the Eureka service registry:
jea-gateway-service.yml
server:
  port: 8085
  
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8090/eureka}
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

Application Properties

The gateway connects to the Config Server for centralized configuration:
application.yml
spring:
  application:
    name: jea-gateway-service
  profiles:
    active: development
  config:
    import: optional:configserver:http://root:123456@localhost:7070

CORS Configuration

Global CORS settings allow the Angular frontend (port 4200) to communicate with the backend:
jea-gateway-service.yml
spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:4200"
            allowedHeaders: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
The CORS configuration allows all headers and common HTTP methods from the localhost:4200 origin.

Service Routes

The gateway defines routes for each microservice using path-based predicates and load-balanced URIs.

Authentication Service (Public)

- id: jea-auth-service
  uri: lb://jea-auth-service
  predicates:
    - Path=/auth/**, /usuario/**, /rol/**
The auth service routes are public and do not require authentication. This allows users to login and obtain JWT tokens.

Protected Services

All other services require authentication via the AuthFilter:
- id: jea-catalogo-service
  uri: lb://jea-catalogo-service
  predicates:
    - Path=/categoria/**, /producto/**, /imagenes/**
  filters:
    - AuthFilter

- id: jea-cliente-service
  uri: lb://jea-cliente-service
  predicates:
    - Path=/cliente/**
  filters:
    - AuthFilter

- id: jea-venta-service
  uri: lb://jea-venta-service
  predicates:
    - Path=/venta/**
  filters:
    - AuthFilter
The lb:// prefix in the URI enables load balancing through Eureka service discovery.

Complete Route Table

ServicePath PatternsAuthentication Required
Auth/auth/**, /usuario/**, /rol/**No
Catalogo/categoria/**, /producto/**, /imagenes/**Yes (except /imagenes/**)
Cliente/cliente/**Yes
Pagos/pagos/**Yes
Venta/venta/**Yes
Pedido/pedido/**Yes
Compra/compra/**Yes
Proveedor/proveedor/**Yes
Inventario/inventario/**Yes

Authentication Filter

The AuthFilter implements JWT token validation for protected routes.

Implementation

AuthFilter.java
package com.example.jeagatewayserver.config;

import com.example.jeagatewayserver.dto.TokenDto;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {

    private WebClient.Builder webClient;

    public AuthFilter(WebClient.Builder webClient) {
        super(Config.class);
        this.webClient = webClient;
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (((exchange, chain) -> {
            String path = exchange.getRequest().getURI().getPath();
            
            // Allow public access to image endpoints
            if (path.startsWith("/imagenes")) {
                return chain.filter(exchange);
            }
            
            // Check for Authorization header
            if(!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION))
                return onError(exchange, HttpStatus.BAD_REQUEST);
            
            String tokenHeader = exchange.getRequest().getHeaders()
                .get(HttpHeaders.AUTHORIZATION).get(0);
            String [] chunks = tokenHeader.split(" ");
            
            // Validate Bearer token format
            if(chunks.length != 2 || !chunks[0].equals("Bearer"))
                return onError(exchange, HttpStatus.BAD_REQUEST);
            
            // Validate token with auth service
            return webClient.build()
                    .post()
                    .uri("http://jea-auth-service/auth/validate?token=" + chunks[1])
                    .retrieve().bodyToMono(TokenDto.class)
                    .map(t -> {
                        t.getToken();
                        return exchange;
                    }).flatMap(chain::filter);
        }));
    }

    public Mono<Void> onError(ServerWebExchange exchange, HttpStatus status){
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(status);
        return response.setComplete();
    }

    public static class Config {}
}

Authentication Flow

  1. Path Exemption: Images at /imagenes/** bypass authentication
  2. Header Validation: Checks for Authorization header presence
  3. Token Format: Validates Bearer <token> format
  4. Token Validation: Calls auth service at /auth/validate endpoint
  5. Response: Returns 400 BAD_REQUEST for invalid tokens

WebClient Configuration

The gateway uses a load-balanced WebClient for inter-service communication:
WebClientConfig.java
package com.example.jeagatewayserver.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {
    @Bean
    @LoadBalanced
    public WebClient.Builder builder() {
        return WebClient.builder();
    }
}
The @LoadBalanced annotation enables Eureka-based service discovery for the WebClient.

Token DTO

TokenDto.java
package com.example.jeagatewayserver.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Builder
@AllArgsConstructor
@Data
public class TokenDto {
    private String token;
}

Maven Dependencies

pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Running the Gateway

Prerequisites

  1. Config Server running on port 7070
  2. Eureka Server running on port 8090
  3. Auth Service registered with Eureka

Startup

mvn spring-boot:run
The gateway will:
  1. Connect to Config Server to fetch configuration
  2. Register with Eureka service registry
  3. Start accepting requests on port 8085

Testing Routes

Public Route (No Authentication)

curl http://localhost:8085/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"123456"}'

Protected Route (With Authentication)

curl http://localhost:8085/producto/all \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Best Practices

  • Always use HTTPS in production
  • Implement rate limiting for public endpoints
  • Use secure credentials for Config Server
  • Rotate JWT signing keys regularly
  • Enable response caching where appropriate
  • Configure connection pool sizes for WebClient
  • Monitor gateway metrics and latency
  • Use circuit breakers for resilience
  • Enable actuator endpoints for health checks
  • Track request/response metrics
  • Log authentication failures
  • Monitor service discovery events

Troubleshooting

Common issues and solutions:
IssueSolution
503 Service UnavailableCheck if target service is registered in Eureka
401 UnauthorizedVerify JWT token is valid and not expired
CORS errorsEnsure frontend origin is in allowedOrigins
Connection refusedVerify Config Server and Eureka are running

Build docs developers (and LLMs) love