Cloud Native 12 min read

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

This tutorial demonstrates how to set up a Spring Cloud Gateway service using SpringBoot 2.1, configure static and dynamic routes with Nacos, implement a custom authentication filter, and manage route updates without restarting the gateway, providing a comprehensive guide for beginners.

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

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>

commons-lang3

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

Route Configuration

The gateway acts as a unified entry point; each route maps to a microservice. The following YAML 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

Configuration Explanation

Demo service runs at 127.0.0.1:8081 , so the route URI is http://localhost:8081 .

Request localhost:8080/demo-server matches the predicate Path=/demo-server/** and is routed to the demo service.

Direct call to the service is localhost:8081/api/test ; through the gateway it becomes localhost:8080/demo-server/api/test . The StripPrefix=1 filter removes the first path segment.

Static configuration requires a service restart to modify routes, which is undesirable for production. The next section shows how to achieve dynamic routing with Nacos.

Dynamic Routing

Deploy a Nacos server (Docker or source) and store route definitions in Nacos. The groupId is the gateway service name, dataId is routes , and the format is JSON.

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

Compare JSON and YAML Configurations

{
    "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 (Dynamic Route Service)

The core of dynamic routing is listening to Nacos configuration changes and updating the gateway routes via the RouteDefinitionWriter.

@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

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

@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; // 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

<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

Summary

Gateway can implement routing via configuration; integrating Nacos and configuration listeners enables dynamic routing without service restarts. Implementing GlobalFilter and Ordered interfaces allows quick creation of custom filters, and the article details the authentication flow after login.

JavaMicroservicesNacosauthenticationDynamic RoutingSpring Cloud Gateway
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.