Cloud Native 12 min read

How to Build a Spring Cloud Gateway with Dynamic Nacos Routing and Auth Filters

This guide walks through setting up a Spring Cloud Gateway service using Spring Boot 2.1, configuring static and dynamic routes via YAML and Nacos JSON, and implementing a custom authentication filter with Redis token validation, covering gateway framework, route definitions, dynamic updates, and security filters.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Build a Spring Cloud Gateway with Dynamic Nacos Routing and Auth Filters

Preface

This article records how I use Spring Cloud Gateway to build a gateway service and achieve dynamic routing, helping readers quickly set up a gateway, understand routing configuration, authentication flow, and business handling. It is suitable as an introductory tutorial for those unfamiliar with gateway services.

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>

common-lang3

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

Route Configuration

The gateway acts as a unified entry point; each route corresponds to a business system entry. By defining routing rules, requests are matched to the appropriate micro‑service.

server:
  port: 8080

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

routes

Interpretation of Configuration

There is a service demo-server deployed locally at 127.0.0.1:8081, so the route uri is http://localhost:8081.

The gateway listens on port 8080; accessing localhost:8080/demo-server triggers the Path=/demo-server/** predicate, routing the request to the demo server.

Direct access to the demo server is localhost:8081/api/test. Through the gateway the address becomes localhost:8080/demo-server/api/test. The StripPrefix=1 filter removes the first path segment, i.e., demo-server.

Using Gateway configuration files to set up routing is convenient, but changes require a service restart, which can cause downtime. The following sections show how to achieve dynamic routing with Nacos.

Dynamic Routing

To implement dynamic routing, we combine Nacos with the gateway server. First, deploy a Nacos instance (Docker or source). Refer to the official documentation for details.

Nacos Configuration

groupId: use the gateway service name.

dataId: routes

Configuration format: JSON

[
    {
        "id": "xxx-server",
        "order": 1, // priority
        "predicates": [{ // routing predicates
            "args": {
                "pattern": "/xxx-server/**"
            },
            "name": "Path"
        }],
        "filters": [{ // filter rules
            "args": {
                "parts": 0 // for k8s internal access, set 0
            },
            "name": "StripPrefix" // start index to strip
        }],
        "uri": "http://localhost:8080/xxx-server" // target address
    }
]

The JSON configuration corresponds to the YAML format; understanding both representations is necessary.

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

Nacos dynamic routing relies on configuration listening; when the configuration changes, the gateway API creates or updates routes.

@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<String> routeIds = Lists.newArrayList();

    @NacosConfigListener(dataId = "routes", groupId = "gateway-server")
    public void routeConfigListener(String configInfo) {
        clearRoute();
        try {
            List<RouteDefinition> 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

Gateway provides GlobalFilter and Ordered interfaces for defining filters. Implementing these interfaces allows custom filter logic.

Typical gateway filters include authentication (login check, blacklist, whitelist) and rate limiting (IP limits). Below is a simple authentication filter implementation.

Authentication Filter

The authentication filter validates the token's effectiveness. The gateway and business services share the same Redis instance.

Add Redis dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

Redis configuration:

spring:
  redis:
    host: redis-server
    port: 6379
    password:
    database: 0

Authentication filter implementation:

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

    @Override
    public Mono<Void> 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.Builder builder = exchange.getRequest().mutate();
        request = builder.header("user_id", userId).build();
        // Extend token expiration
        resetTokenExpirationTime(token, userId);
        return chain.filter(exchange);
    }

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

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

Conclusion

Gateway can achieve routing through configuration items; integrating Nacos and configuration listeners enables dynamic routing. Implementing GlobalFilter and Ordered interfaces allows quick creation of filters. The article also details the authentication flow after login.

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.

dynamic routingSpring Cloud Gateway
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.