28-Spring Cloud Gateway 网关详解

4 阅读7分钟

Spring Cloud Gateway 网关详解

一、知识概述

API 网关是微服务架构中的重要组件,作为系统的统一入口,负责请求路由、协议转换、安全认证、限流熔断等功能。Spring Cloud Gateway 是 Spring Cloud 官方推出的新一代 API 网关,基于 Spring WebFlux 实现,具有高性能、功能强大等特点。

网关的核心功能:

  • 路由转发:将请求转发到后端服务
  • 负载均衡:分发请求到多个实例
  • 认证鉴权:统一的安全控制
  • 限流熔断:保护后端服务
  • 日志监控:请求追踪和监控

理解网关的原理和配置,是构建微服务系统的关键技能。

二、知识点详细讲解

2.1 网关架构

客户端
   │
   ▼
┌─────────────────────────────────────┐
│           Spring Cloud Gateway       │
│  ┌─────────────────────────────────┐ │
│  │         Handler Mapping          │ │
│  └─────────────────────────────────┘ │
│               │                      │
│               ▼                      │
│  ┌─────────────────────────────────┐ │
│  │           Web Handler            │ │
│  │  ┌───────────────────────────┐  │ │
│  │  │      Filter Chain         │  │ │
│  │  │  ┌─────────────────────┐  │  │ │
│  │  │  │   Pre Filters       │  │  │ │
│  │  │  ├─────────────────────┤  │  │ │
│  │  │  │   Route Handler     │  │  │ │
│  │  │  ├─────────────────────┤  │  │ │
│  │  │  │   Post Filters      │  │  │ │
│  │  │  └─────────────────────┘  │  │ │
│  │  └───────────────────────────┘  │ │
│  └─────────────────────────────────┘ │
└─────────────────────────────────────┘
   │
   ▼
后端服务

2.2 核心概念

Route(路由)

网关的基本构建块,包含:

  • id:路由标识
  • uri:目标地址
  • predicates:断言条件
  • filters:过滤器
Predicate(断言)

匹配 HTTP 请求的条件:

  • Path:路径匹配
  • Method:方法匹配
  • Header:头匹配
  • Query:参数匹配
  • Time:时间匹配
Filter(过滤器)

处理请求和响应:

  • Pre Filter:请求前处理
  • Post Filter:响应后处理

2.3 工作流程

1. Handler Mapping 根据 Predicate 匹配路由
    ↓
2. 创建 Filter Chain
    ↓
3. 执行 Pre Filters
    ↓
4. 转发请求到后端服务
    ↓
5. 执行 Post Filters
    ↓
6. 返回响应给客户端

2.4 内置断言工厂

断言说明示例
Path路径匹配Path=/api/user/**
Method方法匹配Method=GET
Header请求头匹配Header=X-Request-Id
Query参数匹配Query=token
CookieCookie 匹配Cookie=session
After时间之后After=2024-01-01T00:00:00+08:00
Before时间之前Before=2024-12-31T23:59:59+08:00
Between时间之间Between=...,...

三、代码示例

3.1 基础路由配置

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
# application.yml
server:
  port: 8080

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        # 用户服务路由
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            
        # 订单服务路由
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            
        # 商品服务路由
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**
          filters:
            - StripPrefix=1

3.2 路由配置类

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
            // 用户服务
            .route("user-service", r -> r
                .path("/api/users/**")
                .filters(f -> f
                    .stripPrefix(1)
                    .addRequestHeader("X-Gateway", "gateway-service")
                    .addResponseHeader("X-Response-Time", 
                        String.valueOf(System.currentTimeMillis()))
                )
                .uri("lb://user-service")
            )
            
            // 订单服务
            .route("order-service", r -> r
                .path("/api/orders/**")
                .and()
                .method("GET", "POST")
                .filters(f -> f
                    .stripPrefix(1)
                    .requestRateLimiter(c -> c
                        .setRateLimiter(userRateLimiter())
                    )
                )
                .uri("lb://order-service")
            )
            
            // 认证服务
            .route("auth-service", r -> r
                .path("/api/auth/**")
                .filters(f -> f
                    .stripPrefix(1)
                    .retry(3)
                )
                .uri("lb://auth-service")
            )
            
            .build();
    }
}

3.3 断言配置

spring:
  cloud:
    gateway:
      routes:
        # 多条件组合
        - id: complex-route
          uri: lb://user-service
          predicates:
            # 路径匹配
            - Path=/api/users/**
            # 方法匹配
            - Method=GET,POST
            # 请求头匹配
            - Header=X-Request-Id, \d+
            # 参数匹配
            - Query=token
            # Cookie 匹配
            - Cookie=session, .+
            # 时间匹配
            - After=2024-01-01T00:00:00+08:00
            # 远程地址匹配
            - RemoteAddr=192.168.1.1/24
            
        # 权重路由(灰度发布)
        - id: user-service-v1
          uri: lb://user-service-v1
          predicates:
            - Path=/api/users/**
            - Weight=group1, 80
            
        - id: user-service-v2
          uri: lb://user-service-v2
          predicates:
            - Path=/api/users/**
            - Weight=group1, 20

3.4 内置过滤器

spring:
  cloud:
    gateway:
      routes:
        - id: filter-example
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            # 路径处理
            - StripPrefix=1                    # 去除前缀
            - PrefixPath=/api                  # 添加前缀
            - RewritePath=/api/(?<segment>.*), /$\{segment}  # 重写路径
            
            # 请求头处理
            - AddRequestHeader=X-Request-Foo, Bar
            - AddRequestParameter=foo, bar
            - RemoveRequestHeader=X-Request-Foo
            
            # 响应头处理
            - AddResponseHeader=X-Response-Foo, Bar
            - RemoveResponseHeader=X-Response-Foo
            - SetResponseHeader=X-Response-Foo, Bar-New
            
            # 重试
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
                methods: GET
                backoff:
                  firstBackoff: 100ms
                  maxBackoff: 500ms
                  factor: 2
                  
            # 限流
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"
                
            # 熔断
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
                fallbackUri: forward:/fallback
                
            # 超时
            - name: RequestSize
              args:
                maxSize: 10MB

3.5 自定义过滤器

import org.springframework.cloud.gateway.filter.*;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

// 自定义全局过滤器
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
    private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求头中的 Token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        // 验证 Token
        if (!validateToken(token)) {
            // 返回 401 未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        // 解析用户信息并传递
        String userId = parseUserId(token);
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", userId)
            .build();
        
        return chain.filter(exchange.mutate().request(request).build());
    }
    
    @Override
    public int getOrder() {
        return -100;  // 高优先级
    }
    
    private boolean validateToken(String token) {
        // Token 验证逻辑
        return token != null && token.startsWith("Bearer ");
    }
    
    private String parseUserId(String token) {
        // 解析用户 ID
        return "user-123";
    }
}

// 自定义网关过滤器
@Component
public class RequestLogFilter implements GlobalFilter, Ordered {
    
    private static final Logger log = LoggerFactory.getLogger(RequestLogFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        long startTime = System.currentTimeMillis();
        
        // 请求日志
        log.info("请求开始: {} {} from {}", 
            request.getMethod(), 
            request.getPath(), 
            request.getRemoteAddress());
        
        // 添加开始时间到属性
        exchange.getAttributes().put("startTime", startTime);
        
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTimeAttr = exchange.getAttribute("startTime");
            if (startTimeAttr != null) {
                long duration = System.currentTimeMillis() - startTimeAttr;
                
                // 响应日志
                log.info("请求完成: {} {} - {} ({}ms)", 
                    request.getMethod(), 
                    request.getPath(),
                    exchange.getResponse().getStatusCode(),
                    duration);
            }
        }));
    }
    
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;  // 低优先级(最后执行)
    }
}

3.6 自定义过滤器工厂

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class AuthGatewayFilterFactory 
        extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
    
    public AuthGatewayFilterFactory() {
        super(Config.class);
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            
            // 获取认证信息
            String token = request.getHeaders().getFirst(config.getHeaderName());
            
            // 验证认证信息
            if (token == null || !validateToken(token)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            
            return chain.filter(exchange);
        };
    }
    
    private boolean validateToken(String token) {
        // 验证逻辑
        return true;
    }
    
    public static class Config {
        private String headerName = "Authorization";
        private boolean required = true;
        
        // getter/setter
        public String getHeaderName() { return headerName; }
        public void setHeaderName(String headerName) { this.headerName = headerName; }
        public boolean isRequired() { return required; }
        public void setRequired(boolean required) { this.required = required; }
    }
}
# 使用自定义过滤器
spring:
  cloud:
    gateway:
      routes:
        - id: auth-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: Auth
              args:
                headerName: X-Token
                required: true

3.7 限流配置

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfig {
    
    // 基于 IP 限流
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    // 基于用户限流
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }
    
    // 基于 API 限流
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getPath().value()
        );
    }
    
    // 基于参数限流
    @Bean
    public KeyResolver parameterKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getQueryParams().getFirst("appId")
        );
    }
}
spring:
  cloud:
    gateway:
      routes:
        - id: rate-limited-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10  # 每秒补充令牌数
                redis-rate-limiter.burstCapacity: 20   # 令牌桶容量
                key-resolver: "#{@ipKeyResolver}"

3.8 熔断配置

spring:
  cloud:
    gateway:
      routes:
        - id: circuit-breaker-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users
                statusCodes:
                  - 500
                  - 502
                  - 503
                  - 504

resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 10
        slidingWindowType: COUNT_BASED
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 5
    instances:
      userServiceCircuitBreaker:
        baseConfig: default
// 降级处理
@RestController
public class FallbackController {
    
    @GetMapping("/fallback/users")
    public Mono<Map<String, Object>> userFallback() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 503);
        result.put("message", "用户服务暂时不可用,请稍后重试");
        result.put("data", null);
        return Mono.just(result);
    }
    
    @GetMapping("/fallback/orders")
    public Mono<Map<String, Object>> orderFallback() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 503);
        result.put("message", "订单服务暂时不可用,请稍后重试");
        result.put("data", null);
        return Mono.just(result);
    }
}

3.9 跨域配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"
            allowCredentials: true
            maxAge: 3600
// 或使用代码配置
@Configuration
public class CorsConfig {
    
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        
        return new CorsWebFilter(source);
    }
}

四、实战应用场景

4.1 统一认证鉴权

import org.springframework.cloud.gateway.filter.*;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import reactor.core.publisher.Mono;

@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {
    
    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    
    // 白名单路径
    private final List<String> whiteList = Arrays.asList(
        "/api/auth/login",
        "/api/auth/register",
        "/api/auth/refresh",
        "/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 = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null) {
            return unauthorized(exchange, "缺少认证信息");
        }
        
        try {
            // 验证 Token
            Claims claims = JwtUtil.parseToken(token);
            String userId = claims.getSubject();
            List<String> roles = claims.get("roles", List.class);
            
            // 检查权限
            if (!checkPermission(path, exchange.getRequest().getMethod(), roles)) {
                return forbidden(exchange, "无权限访问");
            }
            
            // 传递用户信息
            ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-User-Id", userId)
                .header("X-User-Roles", String.join(",", roles))
                .build();
            
            return chain.filter(exchange.mutate().request(request).build());
            
        } catch (Exception e) {
            return unauthorized(exchange, "Token 无效或已过期");
        }
    }
    
    private boolean isWhiteListed(String path) {
        return whiteList.stream()
            .anyMatch(pattern -> pathMatcher.match(pattern, path));
    }
    
    private boolean checkPermission(String path, HttpMethod method, List<String> roles) {
        // 权限检查逻辑
        return true;
    }
    
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        String body = String.format("{\"code\":401,\"message\":\"%s\"}", message);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
        
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }
    
    private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        String body = String.format("{\"code\":403,\"message\":\"%s\"}", message);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
        
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }
    
    @Override
    public int getOrder() {
        return -100;
    }
}

4.2 灰度发布

import org.springframework.cloud.gateway.filter.*;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class GrayReleaseFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取灰度标识
        String grayVersion = exchange.getRequest().getHeaders().getFirst("X-Gray-Version");
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        
        // 根据用户 ID 计算是否灰度
        if (isGrayUser(userId)) {
            exchange.getAttributes().put("grayVersion", "v2");
        } else if (grayVersion != null) {
            exchange.getAttributes().put("grayVersion", grayVersion);
        }
        
        return chain.filter(exchange);
    }
    
    private boolean isGrayUser(String userId) {
        // 按用户 ID 哈希取模,控制灰度比例
        if (userId == null) return false;
        return Math.abs(userId.hashCode()) % 100 < 10;  // 10% 灰度
    }
    
    @Override
    public int getOrder() {
        return -50;
    }
}

五、总结与最佳实践

网关设计原则

  1. 职责单一:网关只做路由和通用逻辑
  2. 无状态:网关不应保存业务状态
  3. 高性能:使用响应式编程
  4. 可扩展:支持动态路由配置

最佳实践

  1. 认证鉴权:在网关统一处理
  2. 限流熔断:保护后端服务
  3. 日志追踪:添加链路追踪信息
  4. 监控告警:监控网关性能指标

Spring Cloud Gateway 是构建微服务架构的重要组件,掌握其使用方式和最佳实践,能够构建出高性能、高可用的 API 网关系统。