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<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
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<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 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
