Cloud Native 12 min read

Building a Spring Cloud Gateway Service with Dynamic Routing via Nacos and Authentication Filter

This tutorial explains how to create a Spring Cloud Gateway service, configure static and dynamic routing using Nacos, and implement a custom authentication filter with Redis token validation, providing a step‑by‑step guide for backend developers building cloud‑native microservice gateways.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Building a Spring Cloud Gateway Service with Dynamic Routing via Nacos and Authentication Filter

Introduction

This article records how to use Spring Cloud Gateway to build a gateway service and achieve dynamic routing, helping beginners quickly set up a gateway, understand routing configuration, authentication flow, and business processing.

Service Setup

Framework

SpringBoot 2.1

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.1.0.RELEASE</version>
</parent>

Spring‑cloud‑gateway‑core

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-gateway-core</artifactId>
</dependency>

commons‑lang3

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
</dependency>

Routing Configuration

The gateway acts as a unified entry point; each route maps to a microservice. The following YAML config defines a static route to a demo service.

server:
  port: 8080

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

Explanation:

Demo service runs at 127.0.0.1:8081 (URI http://localhost:8081 ).

Requests to localhost:8080/demo-server/** are routed to the demo service.

The StripPrefix=1 filter removes the first path segment so the downstream service receives /api/test instead of /demo-server/api/test .

Static routing requires a service restart to apply changes, which is undesirable for production.

Dynamic Routing

Dynamic routing is achieved by integrating Nacos with the gateway. Deploy a Nacos instance (Docker or binary) and store route definitions in Nacos as JSON.

Nacos Configuration

Use the gateway service name as groupId , set dataId to routes , and store JSON like the following:

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

The JSON structure mirrors the YAML configuration; understanding both formats is essential.

JSON vs YAML Comparison

{
  "id":"demo-server",
  "predicates":[{"args":{"pattern":"/demo-server/**"},"name":"Path"}],
  "filters":[{"args":{"parts":1},"name":"StripPrefix"}],
  "uri":"http://localhost:8081"
}
spring:
  cloud:
    gateway:
      enabled: true
      routes:
        - id: demo-server
          uri: http://localhost:8081
          predicates:
            - Path=/demo-server/**
          filters:
            - StripPrefix= 1

Code Implementation

The core of Nacos‑based dynamic routing is a listener that reacts to configuration changes and updates the gateway routes via the RouteDefinitionWriter API.

@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);
        }
    }

    private void clearRoute() {
        for (String id : routeIds) {
            routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        routeIds.clear();
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    private void addRoute(RouteDefinition definition) {
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            routeIds.add(definition.getId());
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    private void publish() {
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
    }
}

Filters

The gateway provides GlobalFilter and Ordered interfaces for custom filters. Below is a simple authentication filter that validates a token stored in Redis.

Authentication Filter

@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();
        }
        // Add user_id header for downstream services
        ServerHttpRequest mutated = exchange.getRequest().mutate().header("user_id", userId).build();
        exchange = exchange.mutate().request(mutated).build();
        // Refresh token expiration
        resetTokenExpirationTime(token, userId);
        return chain.filter(exchange);
    }

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

    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;
    }
}

To support token validation, add the reactive Redis starter dependency and configure Redis connection:

<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

Spring Cloud Gateway can implement routing through configuration files; integrating Nacos enables dynamic routing without service restarts. By implementing GlobalFilter and Ordered , developers can quickly add authentication, rate‑limiting, or other cross‑cutting concerns. The article also details the token‑validation flow and how to refresh token expiration in Redis.

javaMicroservicesNacosauthenticationdynamic routingSpring Cloud Gateway
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.