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.
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= 1Configuration 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= 1Code 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: 0Summary
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.
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.
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.