Cloud Native 13 min read

Spring Cloud Gateway: Unified Entry Point for Microservice Calls

This tutorial walks through adding Spring Cloud Gateway as a single entry point for client calls to multiple microservices, covering why a gateway is needed, project structure, dependency and configuration setup, global logging and authentication filters, startup verification, and common pitfalls with solutions.

Coder Trainee
Coder Trainee
Coder Trainee
Spring Cloud Gateway: Unified Entry Point for Microservice Calls

Goal

┌─────────────────────────────────────────────────────────────┐
│               Final Effect of This Episode                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Client ──► Gateway(:8080) ──┬──► user-service(:8081)   │
│                              │          │                │
│                              └──► order-service(:8082) │
│                                                             │
│   ✅ Unified entry: all requests go through the gateway      │
│   ✅ Route forwarding based on path                           │
│   ✅ Centralized authentication                              │
│   ✅ Global CORS configuration                              │
└─────────────────────────────────────────────────────────────┘

Why a Gateway?

Problems without a gateway

Clients must remember dozens of service URLs.

Each service needs its own CORS configuration.

Login verification is duplicated in every service.

Changing a service address requires updating all clients.

Architecture after adding a gateway

┌─────────────────────────────────────┐
                │           Gateway (:8080)            │
                │  http://api.teaching.com             │
                └─────────────────┬───────────────────┘
                                  │
                ┌─────────────────┼───────────────────┐
                │                 │                 │
                ▼                 ▼                 ▼
          ┌──────────┐     ┌──────────┐     ┌──────────┐
          │ User svc │     │ Order svc│     │ Product  │
          │ :8081    │     │ :8082    │     │ :8083    │
          └──────────┘     └──────────┘     └──────────┘

Advantages

Clients interact only with the gateway.

Unified authentication, logging and rate limiting.

Service address changes are transparent to clients.

Project Structure (new gateway module)

spring-cloud-teaching-ep04/
├── pom.xml                 # parent project, adds gateway dependency management
├── docker-compose.yml
├── user-service/           # same as previous episodes
│   └── ...
├── order-service/          # same as previous episodes
│   └── ...
├── gateway/                # ★ added in this episode
│   ├── pom.xml
│   └── src/main/java/com/teaching/gateway/
│       ├── GatewayApplication.java
│       ├── config/
│       │   └── GatewayConfig.java      # route configuration
│       └── filter/
│           ├── AuthGlobalFilter.java # global auth filter
│           └── LogGlobalFilter.java  # logging filter
└── README.md

Gateway Module Details

1. Dependency Configuration (gateway/pom.xml)

<!-- gateway/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.teaching</groupId>
        <artifactId>spring-cloud-teaching</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>gateway</artifactId>
    <dependencies>
        <!-- ★ Gateway dependency (do NOT add spring-boot-starter-web) -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Nacos service discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- Load balancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

2. Configuration File (gateway/src/main/resources/application.yml)

# gateway/src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

gateway:
  routes:
    - id: user-service-route
      uri: lb://user-service   # lb:// means load‑balanced
      predicates:
        - Path=/api/user/**
      filters:
        - StripPrefix=0
    - id: order-service-route
      uri: lb://order-service
      predicates:
        - Path=/api/order/**
      filters:
        - StripPrefix=0
  httpclient:
    connect-timeout: 3000
    response-timeout: 30s
  logging:
    level:
      org.springframework.cloud.gateway: DEBUG

3. Startup Class

// gateway/src/main/java/com/teaching/gateway/GatewayApplication.java
package com.teaching.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
        System.out.println("✅ Gateway started successfully! Port: 8080");
    }
}

4. Global Logging Filter

// gateway/src/main/java/com/teaching/gateway/filter/LogGlobalFilter.java
package com.teaching.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class LogGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        String path = exchange.getRequest().getURI().getPath();
        String method = exchange.getRequest().getMethod().name();
        log.info("Request incoming: {} {}", method, path);
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long duration = System.currentTimeMillis() - startTime;
            int status = exchange.getResponse().getStatusCode() != null ?
                    exchange.getResponse().getStatusCode().value() : 0;
            log.info("Request completed: {} {} - {} ({}ms)", method, path, status, duration);
        }));
    }

    @Override
    public int getOrder() {
        return -1; // high priority
    }
}

5. Global Authentication Filter (example)

// gateway/src/main/java/com/teaching/gateway/filter/AuthGlobalFilter.java
package com.teaching.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private static final String[] WHITE_LIST = {
            "/api/user/login",
            "/api/user/register",
            "/actuator/health"
    };

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        // 1. White‑list pass‑through
        if (isWhiteList(path)) {
            return chain.filter(exchange);
        }
        // 2. Get Token
        String token = getToken(request);
        if (!StringUtils.hasText(token)) {
            return unauthorized(exchange, "Missing Token");
        }
        // 3. Token validation (simplified)
        if (!validateToken(token)) {
            return unauthorized(exchange, "Invalid or expired Token");
        }
        // 4. Extract user info and inject into header
        String userId = extractUserId(token);
        ServerHttpRequest mutatedRequest = request.mutate()
                .header("X-User-Id", userId)
                .build();
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    private boolean isWhiteList(String path) {
        for (String white : WHITE_LIST) {
            if (path.startsWith(white)) {
                return true;
            }
        }
        return false;
    }

    private String getToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private boolean validateToken(String token) {
        // Simplified: token must start with "valid_"
        return token.startsWith("valid_");
    }

    private String extractUserId(String token) {
        // Simplified: always return "1"
        return "1";
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
        String body = String.format("{\"code\":401,\"message\":\"%s\"}", message);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100; // highest priority, before logging filter
    }
}

Startup Verification

1. Start all services

# 1. Start Nacos
docker-compose up -d

# 2. Start user-service
cd user-service
mvn spring-boot:run

# 3. Start order-service
cd ../order-service
mvn spring-boot:run

# 4. Start gateway (new terminal)
cd ../gateway
mvn spring-boot:run

2. Verify routing

# Call user-service via gateway
curl http://localhost:8080/api/user/1

# Call order-service (which may call user-service) via gateway
curl http://localhost:8080/api/order/1

3. Verify Nacos registration

Visit http://localhost:8848/nacos; the service list should contain:

user-service
order-service
gateway

Common Issues & Pitfalls

Pitfall 1: Gateway and Web dependency conflict

Symptom: Startup error "Spring MVC found on classpath".

Cause: Gateway is based on WebFlux and cannot coexist with spring-boot-starter-web.

Solution: Ensure the gateway module does not include the web starter.

Pitfall 2: Routes not taking effect

Debug steps:

Check that the predicates path patterns are correct.

Confirm the services are registered in Nacos.

Enable Gateway debug logs by setting org.springframework.cloud.gateway: TRACE in logging.level.

Pitfall 3: lb:// service name not found

Cause: Missing load‑balancer dependency or mismatched service name.

Solution: Add the spring-cloud-starter-loadbalancer dependency (as shown in the pom snippet) and ensure the service name matches the registration.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MicroservicesNacosAuthenticationLoad BalancerSpring Cloud GatewayGlobal Filter
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.