Backend Development 11 min read

Building a Spring Cloud Gateway Service with Dynamic Routing Using Nacos

This tutorial demonstrates how to set up a Spring Cloud Gateway service with static route configuration, then extend it to support dynamic routing through Nacos, and finally implement custom authentication and global filters using Java and Spring Boot.

Architecture Digest
Architecture Digest
Architecture Digest
Building a Spring Cloud Gateway Service with Dynamic Routing Using Nacos

The article begins with a brief introduction explaining the purpose of the guide: to help beginners quickly build a gateway service using Spring Cloud Gateway, understand route configuration, authentication flow, and dynamic routing.

Service Setup

It specifies the required framework versions (Spring Boot 2.1) and lists Maven dependencies for spring-boot-starter-parent , spring-cloud-gateway-core , and commons-lang3 .

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.0.RELEASE</version>
</parent>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gateway-core</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

Static Route Configuration

The gateway runs on port 8080 and forwards requests to a demo service at http://localhost:8081 using a YAML route definition with a Path=/demo-server/** predicate and a StripPrefix=1 filter.

server:
  port: 8080

spring:
  cloud:
    gateway:
      enabled: true
      routes:
        - id: demo-server
          uri: http://localhost:8081
          predicates:
            - Path=/demo-server/**
          filters:
            - StripPrefix= 1

It explains how requests are matched and routed, and notes that changing static routes requires a service restart.

Dynamic Routing with Nacos

To avoid restarts, the guide introduces Nacos as a configuration center. It describes deploying Nacos (e.g., via Docker) and storing route definitions in a JSON dataId named routes under the group gateway-server .

[
  {
    "id": "xxx-server",
    "order": 1,
    "predicates": [{
      "args": {"pattern": "/xxx-server/**"},
      "name": "Path"
    }],
    "filters": [{
      "args": {"parts": 0},
      "name": "StripPrefix"
    }],
    "uri": "http://localhost:8080/xxx-server"
  }
]

It compares the JSON format with the equivalent YAML configuration.

Dynamic Route Service Implementation

The NacosDynamicRouteService class listens to Nacos configuration changes using @NacosConfigListener , parses the JSON into RouteDefinition objects, clears existing routes, adds new ones, and publishes a RefreshRoutesEvent to make them effective.

@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(NacosDynamicRouteService.class);
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher applicationEventPublisher;
    private static List
routeIds = Lists.newArrayList();

    @NacosConfigListener(dataId = "routes", groupId = "gateway-server")
    public void routeConfigListener(String configInfo) {
        clearRoute();
        try {
            List
gatewayRouteDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
            for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                addRoute(routeDefinition);
            }
            publish();
            LOGGER.info("Dynamic Routing Publish Success");
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }
    // clearRoute, addRoute, publish, setApplicationEventPublisher methods omitted for brevity
}

Custom Global Filter and Authentication

The guide shows how to create a global filter by implementing GlobalFilter and Ordered . The AuthFilter extracts a token from headers, query parameters, or cookies, validates it against Redis, returns 401 for missing or invalid tokens, and injects the user ID into the request header for downstream services.

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Autowired
    private RedisTemplate
redisTemplate;
    private static final String TOKEN_HEADER_KEY = "auth_token";

    @Override
    public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = getToken(request);
        ServerHttpResponse response = exchange.getResponse();
        if (StringUtils.isBlank(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        String userId = getUserIdByToken(token);
        if (StringUtils.isBlank(userId)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
        request = builder.header("user_id", userId).build();
        resetTokenExpirationTime(token, userId);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() { return 0; }

    private String getUserIdByToken(String token) {
        String redisKey = String.join(":", "auth_token", token);
        return redisTemplate.opsForValue().get(redisKey);
    }

    private void resetTokenExpirationTime(String token, String userId) {
        String redisKey = String.join(":", "auth_token", token);
        redisTemplate.opsForValue().set(redisKey, userId, 2, TimeUnit.HOURS);
    }

    private static String getToken(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(TOKEN_HEADER_KEY);
        if (StringUtils.isBlank(token)) {
            token = request.getQueryParams().getFirst(TOKEN_HEADER_KEY);
        }
        if (StringUtils.isBlank(token)) {
            HttpCookie cookie = request.getCookies().getFirst(TOKEN_HEADER_KEY);
            if (cookie != null) token = cookie.getValue();
        }
        return token;
    }
}

It also adds the Redis dependency and configuration needed for token storage.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
  redis:
    host: redis-server
    port: 6379
    password:
    database: 0

Conclusion

The article summarizes that Spring Cloud Gateway can handle static routing via configuration, achieve dynamic routing by integrating Nacos and listening to configuration changes, and extend functionality with custom global filters such as authentication, providing a complete beginner-friendly guide.

BackendJavaMicroservicesNacosAuthenticationDynamic RoutingSpring Cloud Gateway
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login 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.