Building a Spring Cloud Gateway Service with Nacos Dynamic Routing and Authentication Filter
This tutorial explains how to quickly set up a Spring Cloud Gateway service, configure static and Nacos‑driven dynamic routes, and implement a Redis‑backed authentication filter that validates tokens, refreshes expiration, and forwards user information to downstream services.
In this article the author, a senior architect, demonstrates step‑by‑step how to create a gateway service using Spring Cloud Gateway 2.1, configure routes in application.yml , and understand the role of each routing property.
First, add the required Maven dependencies for Spring Boot, Spring Cloud Gateway, and Commons Lang3:
<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>Next, define the static route configuration in application.yml :
server:
port: 8080
spring:
cloud:
gateway:
enabled: true
routes:
- id: demo-server
uri: http://localhost:8081
predicates:
- Path=/demo-server/**
filters:
- StripPrefix=1The table below explains each field (id, uri, predicates, filters). The StripPrefix filter removes the first path segment so that the downstream service receives a clean request path.
To avoid restarting the gateway when routes change, the article introduces dynamic routing with Nacos. After deploying a Nacos instance, the route definitions are stored as JSON in a dataId named routes . The following component listens for changes and updates the gateway at runtime:
@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 rd : gatewayRouteDefinitions) {
addRoute(rd);
}
publish();
LOGGER.info("Dynamic Routing Publish Success");
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
// clearRoute, addRoute, publish, setApplicationEventPublisher implementations omitted for brevity
}For security, the article adds a global authentication filter that extracts a token from headers, query parameters, or cookies, checks its validity against Redis, refreshes the token’s TTL, and injects the user ID into the request header for downstream services.
@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 = request.mutate();
request = builder.header("user_id", userId).build();
resetTokenExpirationTime(token, userId);
return chain.filter(exchange);
}
@Override
public int getOrder() { return 0; }
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;
}
}Finally, the article reminds readers that the gateway can be extended with additional filters for rate‑limiting, blacklist checks, etc., and that the whole solution demonstrates a practical, production‑ready approach to routing and authentication in a micro‑service architecture.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.