How to Build a Spring Cloud Gateway with Dynamic Routing via Nacos

Learn step‑by‑step how to set up a Spring Cloud Gateway service, configure static routes, enable dynamic routing using Nacos, and implement authentication filters with Redis, providing a complete guide for building a robust backend gateway in Java.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
How to Build a Spring Cloud Gateway with Dynamic Routing via Nacos

Introduction

This article explains how to use Spring Cloud Gateway to build a gateway service, configure static routes, and achieve dynamic routing with Nacos, while also covering authentication flow and filter implementation for beginners.

Dependencies

<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 acts as a unified entry point; each route maps to a downstream microservice. A typical YAML configuration looks like this:

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

Configuration Item

Description

id

Unique route identifier, usually the service name

uri

Target address of the downstream service

predicates

Route matching rules

filters

Filter rules applied to the route

Dynamic Routing with Nacos

By integrating Nacos, routes can be updated without restarting the gateway. Deploy a Nacos instance (Docker or binary) and store route definitions as JSON.

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

The equivalent YAML configuration is:

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

Dynamic Route Service Implementation

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

Authentication Filter

The gateway can enforce authentication by implementing a GlobalFilter and Ordered filter that validates a token stored in Redis.

Authentication flow diagram
Authentication flow diagram
@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();
        }
        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; // higher 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;
    }
}

Redis Dependency and Configuration

<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

Gateway can realize routing through configuration, and integrating Nacos enables dynamic route updates without service downtime. Implementing GlobalFilter and Ordered interfaces allows quick creation of authentication filters, and the article also details the token validation process using Redis.

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.

JavaNacosdynamic routingSpring Cloud Gateway
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

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.