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.
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= 1Explanation:
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= 1Code 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: 0Conclusion
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.