侧边栏壁纸
  • 累计撰写 72 篇文章
  • 累计创建 28 个标签
  • 累计收到 21 条评论

目 录CONTENT

文章目录

Spring Cloud Gateway 微服务网关实战全攻略:路由、过滤器、限流与鉴权一体化

Administrator
2026-03-26 / 0 评论 / 0 点赞 / 0 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

一、为什么选择 Spring Cloud Gateway?

在微服务架构中,API 网关是所有外部请求的统一入口,承担着以下职责:

职责说明
路由转发根据请求路径/Header 动态路由到对应服务
统一鉴权JWT 验证、OAuth2 集成
限流熔断保护下游服务不被流量打垮
日志追踪统一记录请求链路信息
跨域处理全局 CORS 配置

为什么不用 Zuul?

  • Zuul 1.x 基于 Servlet 阻塞 IO,性能瓶颈明显
  • Spring Cloud Gateway 基于 Reactor + WebFlux,天然支持响应式、非阻塞
  • 官方社区活跃,Spring Boot 3.x / GraalVM 原生支持

二、环境准备

<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Gateway 核心,内置 WebFlux,不能引入 spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- Nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2023.0.1.0</version>
    </dependency>
    <!-- Redis 响应式客户端(限流使用) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- JWT 鉴权 -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.12.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.12.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.12.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

⚠️ 注意:Gateway 基于 WebFlux,项目中不能引入 spring-boot-starter-web,否则启动报错。


三、基础路由配置

3.1 YAML 静态路由

# application.yml
server:
  port: 8080

spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      # 开启从注册中心自动路由(lb://serviceName)
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true  # 服务名小写
      routes:
        # 路由 1:用户服务
        - id: user-service-route
          uri: lb://user-service          # lb:// 表示负载均衡
          predicates:
            - Path=/api/user/**           # 路径断言
          filters:
            - StripPrefix=1              # 去掉第一段路径前缀 /api
        
        # 路由 2:订单服务(带版本前缀)
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - Header=X-Request-Version, v2  # Header 断言
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Gateway-Source, gateway  # 添加请求头
        
        # 路由 3:固定 URL(第三方服务)
        - id: external-service-route
          uri: https://api.external.com
          predicates:
            - Path=/ext/**
          filters:
            - RewritePath=/ext/(?<segment>.*), /$\{segment}  # 路径重写

3.2 路由断言(Predicates)常用类型

断言示例说明
PathPath=/api/**路径匹配
MethodMethod=GET,POST请求方法
HeaderHeader=Token, \d+Header 正则匹配
QueryQuery=userId, \d+查询参数匹配
AfterAfter=2026-01-01T00:00:00+08:00[Asia/Shanghai]时间后有效
RemoteAddrRemoteAddr=192.168.1.0/24IP 段白名单

四、过滤器链详解

4.1 内置 GatewayFilter

filters:
  - StripPrefix=1                           # 去除 N 个路径前缀
  - AddRequestHeader=X-Trace-Id, #{T(java.util.UUID).randomUUID()}
  - AddResponseHeader=X-Frame-Options, DENY
  - SetStatus=200                           # 强制修改响应状态码
  - Retry=3                                 # 失败重试 3 次
  - RequestRateLimiter:                     # 限流(详见第六节)
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20

4.2 全局过滤器(GlobalFilter)— 统一日志

@Component
@Slf4j
public class AccessLogFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();
        String method = request.getMethod().name();
        String ip = getClientIp(request);
        long startTime = System.currentTimeMillis();

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long cost = System.currentTimeMillis() - startTime;
            int statusCode = exchange.getResponse().getStatusCode() != null
                    ? exchange.getResponse().getStatusCode().value() : 0;
            log.info("[Gateway] {} {} | IP={} | Status={} | Cost={}ms",
                    method, path, ip, statusCode, cost);
        }));
    }

    private String getClientIp(ServerHttpRequest request) {
        String ip = request.getHeaders().getFirst("X-Forwarded-For");
        if (ip == null || ip.isBlank()) {
            ip = request.getRemoteAddress() != null
                    ? request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
        }
        return ip;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 1;  // 最后执行,保证 cost 准确
    }
}

五、JWT 全局鉴权过滤器

5.1 JWT 工具类

@Component
public class JwtUtils {

    @Value("${jwt.secret:your-256-bit-secret-key-here-must-be-long-enough}")
    private String secretKey;

    private SecretKey getSignKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 验证并解析 Token
     */
    public Claims validateToken(String token) {
        return Jwts.parser()
                .verifyWith(getSignKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    public boolean isTokenValid(String token) {
        try {
            validateToken(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }
}

5.2 鉴权全局过滤器

@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtUtils jwtUtils;

    /** 白名单路径(不需要鉴权) */
    private static final List<String> WHITE_LIST = List.of(
            "/api/auth/login",
            "/api/auth/register",
            "/api/public/",
            "/actuator/"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();

        // 白名单直接放行
        if (isWhiteListed(path)) {
            return chain.filter(exchange);
        }

        // 获取 Token
        String token = extractToken(exchange.getRequest());
        if (token == null || !jwtUtils.isTokenValid(token)) {
            return unauthorized(exchange, "Token 无效或已过期");
        }

        // Token 合法:将用户信息传递给下游服务
        Claims claims = jwtUtils.validateToken(token);
        String userId = claims.getSubject();
        String roles = claims.get("roles", String.class);

        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                .header("X-User-Id", userId)
                .header("X-User-Roles", roles != null ? roles : "")
                .build();

        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    private boolean isWhiteListed(String path) {
        return WHITE_LIST.stream().anyMatch(path::startsWith);
    }

    private String extractToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String body = """
                {"code": 401, "message": "%s"}
                """.formatted(message);
        DataBuffer buffer = response.bufferFactory()
                .wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100;  // 高优先级,在限流之前执行
    }
}

六、Redis 令牌桶限流

Spring Cloud Gateway 内置了基于 Redis 的令牌桶限流,使用 Lua 脚本保证原子性。

6.1 限流 Key 解析器(按用户 ID)

@Configuration
public class RateLimiterConfig {

    /**
     * 按用户 ID 限流(鉴权后 Header 中有 X-User-Id)
     */
    @Bean
    @Primary
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
            return Mono.just(userId != null ? userId : "anonymous");
        };
    }

    /**
     * 按 IP 限流(兜底策略)
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> {
            String ip = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
            if (ip == null && exchange.getRequest().getRemoteAddress() != null) {
                ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            }
            return Mono.just(ip != null ? ip : "unknown");
        };
    }
}

6.2 路由中配置限流

spring:
  data:
    redis:
      host: localhost
      port: 6379

  cloud:
    gateway:
      routes:
        - id: order-service-rate-limited
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒补充速率
                redis-rate-limiter.replenishRate: 10
                # 令牌桶容量(允许突发)
                redis-rate-limiter.burstCapacity: 20
                # 每次请求消耗令牌数
                redis-rate-limiter.requestedTokens: 1
                # 使用哪个 KeyResolver Bean
                key-resolver: "#{@userKeyResolver}"

6.3 限流响应状态码说明

状态码含义
200正常通过
429Too Many Requests,触发限流

可通过自定义过滤器修改限流拦截后的响应体:

@Component
public class RateLimitResponseFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            if (HttpStatus.TOO_MANY_REQUESTS.equals(response.getStatusCode())) {
                // 可以在此记录指标、推送告警等
                log.warn("[Gateway] Rate limited: {}", exchange.getRequest().getPath());
            }
        }));
    }

    @Override
    public int getOrder() { return Ordered.LOWEST_PRECEDENCE; }
}

七、全局跨域(CORS)配置

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");          // 生产环境替换为具体域名
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

⚠️ 注意:Gateway 中配置 CORS 后,下游服务不要再配置 CORS,否则会出现 Header 重复导致浏览器报错。


八、整合 Nacos 动态路由(高级)

静态路由每次修改都需要重启网关,通过 Nacos 配置中心可实现零停机热更新路由

@Component
@Slf4j
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    /**
     * 更新路由(Nacos 配置变更时调用)
     */
    public void updateRoutes(List<RouteDefinition> routeDefinitions) {
        // 1. 清除旧路由
        clearRoutes();
        // 2. 加载新路由
        routeDefinitions.forEach(route -> {
            routeDefinitionWriter.save(Mono.just(route)).subscribe();
            log.info("[Gateway] Dynamic route updated: {}", route.getId());
        });
        // 3. 发布刷新事件
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    private void clearRoutes() {
        // 实际项目中需要维护已加载路由 ID 列表,此处简化示意
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
}

Nacos 中存储路由配置格式(JSON):

[
  {
    "id": "user-service-route",
    "uri": "lb://user-service",
    "predicates": [
      { "name": "Path", "args": { "pattern": "/api/user/**" } }
    ],
    "filters": [
      { "name": "StripPrefix", "args": { "parts": "1" } }
    ],
    "order": 1
  }
]

九、常见问题与踩坑

9.1 引入 spring-boot-starter-web 导致启动失败

报错Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway

解决:删除 spring-boot-starter-web 依赖,Gateway 已内置 WebFlux。


9.2 使用 LoadBalancer 时找不到服务

报错503 Service Unavailable - No instances available

排查步骤

  1. 确认 spring-cloud-starter-loadbalancer 已引入
  2. 检查 Nacos 服务注册状态
  3. 确认 uri: lb://service-name 中的服务名与 Nacos 注册名一致(注意大小写)

9.3 WebFlux 中不能使用 ThreadLocal

由于响应式编程线程切换,ThreadLocal 传递会丢失,推荐使用 Context

// 存入 Context
return chain.filter(exchange)
    .contextWrite(Context.of("userId", userId));

// 从 Context 取出(在响应式链中)
Mono.deferContextual(ctx -> {
    String userId = ctx.get("userId");
    // ...
});

9.4 Redis 限流 Lua 脚本报错

报错NOSCRIPT No matching script

原因:Redis 集群模式下 Lua 脚本 Key 不在同一 slot。

解决:使用 {user}:key 形式的 Hash Tag,确保相关 key 落在同一槽位。


十、生产部署建议

# Dockerfile(结合前几篇 Docker 多阶段构建文章)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/gateway-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xms256m", "-Xmx512m", \
  "-Dreactor.netty.http.server.accessLogEnabled=true", \
  "-jar", "app.jar"]

性能调优参数

参数推荐值说明
-Xms / -Xmx256m / 1g根据实际流量调整
reactor.netty.ioWorkerCountCPU 核心数 × 2Netty IO 线程数
spring.cloud.gateway.httpclient.connect-timeout3000下游连接超时(ms)
spring.cloud.gateway.httpclient.response-timeout10s下游响应超时

十一、总结

本文完整演示了 Spring Cloud Gateway 在 Spring Boot 3.x 微服务架构中的实战应用:

  • 路由配置:静态路由、断言规则、负载均衡
  • 全局过滤器:访问日志、JWT 鉴权、响应修改
  • Redis 限流:令牌桶算法、按用户/IP 维度限流
  • 跨域配置:全局 CORS,避免重复配置
  • 动态路由:结合 Nacos 实现零停机热更新
  • 踩坑总结:WebFlux 常见问题与解决方案

与前几篇文章形成完整的微服务技术栈:

Gateway(本文)→ Sentinel 限流熔断 → Seata 分布式事务 → RocketMQ 异步解耦 → Docker 容器化部署

如有问题欢迎在评论区留言交流 🚀


参考文档:

0

评论区