Skip to content

Spring Cloud Gateway

导航目录

一、Gateway 概述

1.1 什么是 Gateway

Spring Cloud Gateway 是 Spring Cloud 官方提供的 API 网关组件,构建在 Spring WebFlux 之上。

它的主要作用是统一接收外部请求,再把请求路由到内部微服务。

1.2 Gateway 能做什么

  • 统一路由转发
  • 统一鉴权
  • 统一限流
  • 统一日志
  • 统一跨域处理
  • 统一灰度能力接入

重点:Gateway 的核心定位是“流量入口”和“治理入口”。

1.3 为什么不用 Nginx 代替 Gateway

Nginx 擅长:

  • 反向代理
  • 静态资源服务
  • 负载均衡

Gateway 擅长:

  • 基于服务名动态路由
  • 与 Spring Cloud 生态深度集成
  • 过滤器编程扩展
  • 配合 Nacos、Sentinel 做治理

通常实际项目里会同时使用:

  • Nginx 负责流量入口和反向代理
  • Gateway 负责微服务层的业务网关治理

1.4 Gateway 和 Zuul 的区别

对比项GatewayZuul 1.x
底层模型WebFlux / ReactorServlet
性能更高相对较低
编程模型响应式阻塞式
与 Spring Cloud 新生态集成更好一般

二、为什么需要网关

2.1 没有网关会出现什么问题

  • 客户端直接访问多个服务,调用复杂
  • 每个服务都要重复写鉴权逻辑
  • 每个服务都要单独处理跨域
  • 接口限流、日志、黑白名单难以统一

2.2 网关的核心职责

2.2.1 统一入口

  • 所有外部请求先到网关

2.2.2 统一路由

  • 根据规则把请求转发到目标服务

2.2.3 统一安全控制

  • Token 校验
  • 黑白名单
  • IP 限制

2.2.4 统一流量治理

  • 限流
  • 熔断
  • 灰度

2.3 典型调用链

text
浏览器 / App -> Nginx -> Gateway -> user-service / order-service / pay-service

重点:在微服务体系里,网关通常是外部请求进入系统的第一站。

三、Gateway 核心概念

3.1 Route 路由

Route 是 Gateway 中最核心的概念,一条路由通常包含:

  • id
  • uri
  • predicates
  • filters

3.2 Predicate 路由断言

Predicate 用于判断当前请求是否命中某条路由。

常见断言:

  • Path
  • Method
  • Header
  • Query
  • Host
  • After / Before / Between

3.3 Filter 过滤器

Filter 用于在请求转发前后执行逻辑。

常见用途:

  • 给请求头加参数
  • 统一鉴权
  • 记录日志
  • 修改响应

3.4 全局过滤器和局部过滤器

3.4.1 局部过滤器

  • 只作用于某条路由

3.4.2 全局过滤器

  • 作用于所有请求

3.5 Gateway 工作流程

text
请求进入网关 -> 匹配路由断言 -> 执行过滤器链 -> 转发到目标服务 -> 返回响应

四、路由配置与断言

4.1 引入依赖

xml
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <!-- Gateway 核心依赖 -->
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <!-- 用于和 Nacos 注册中心整合 -->
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

4.2 基础路由配置

yaml
server:
  # 网关服务端口
  port: 10010

spring:
  application:
    # 网关服务名
    name: gateway-service
  cloud:
    nacos:
      # 注册中心地址
      server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: user-service-route
          # lb:// 表示从注册中心按服务名负载均衡转发
          uri: lb://user-service
          predicates:
            # 当请求路径匹配 /user/** 时命中当前路由
            - Path=/user/**

4.3 多路由配置示例

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/order/**

4.4 常见断言示例

4.4.1 Path 断言

yaml
- Path=/api/**

4.4.2 Method 断言

yaml
- Method=GET,POST

4.4.3 Header 断言

yaml
- Header=token, .+

4.4.4 Query 断言

yaml
- Query=id, \\d+

4.4.5 After 断言

yaml
- After=2026-04-04T00:00:00+08:00[Asia/Shanghai]

4.5 StripPrefix 示例

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: product-route
          uri: lb://product-service
          predicates:
            - Path=/api/product/**
          filters:
            # 转发前去掉一层路径前缀 /api
            - StripPrefix=1

重点:uri + predicates + filters 是一条路由最核心的三部分。

五、过滤器机制

5.1 过滤器的作用

过滤器可以在请求进入目标服务前后做统一处理。

常见场景:

  • 请求头加工
  • Token 校验
  • 请求日志
  • 响应头设置

5.2 局部过滤器示例

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            # 给请求增加请求头
            - AddRequestHeader=X-Gateway, gateway-service
            # 给响应增加响应头
            - AddResponseHeader=X-Response-From, gateway

5.3 自定义全局过滤器示例

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("token");

        // 没有 token 时直接返回 401
        if (token == null || token.isBlank()) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 校验通过后继续执行后续过滤器和路由逻辑
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 值越小优先级越高
        return 0;
    }
}

5.4 自定义日志过滤器示例

java
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        System.out.println("gateway request path: " + path);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 让日志过滤器稍后执行
        return 1;
    }
}

5.5 过滤器学习重点

  1. 局部过滤器和全局过滤器区别
  2. 过滤器执行顺序
  3. 请求前置处理和响应后置处理
  4. 鉴权过滤器
  5. 日志过滤器

六、与 Nacos 整合实现动态路由

6.1 为什么要和 Nacos 整合

如果网关写死目标地址:

  • 不利于扩缩容
  • 不利于服务上下线
  • 不利于环境切换

通过 Nacos,Gateway 可以直接按服务名路由。

6.2 注册中心整合配置

yaml
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        # 网关自身也可以注册到 Nacos
        namespace: dev-namespace-id
        group: DEFAULT_GROUP
    gateway:
      discovery:
        locator:
          # 开启后可根据服务名自动创建动态路由
          enabled: true
          # 是否把服务名转成小写
          lower-case-service-id: true

6.3 动态路由说明

开启自动发现后:

  • user-service 可通过 /user-service/** 访问
  • order-service 可通过 /order-service/** 访问

6.4 动态路由的使用建议

  • 学习阶段可以开启,方便快速验证
  • 生产环境通常仍建议显式配置核心路由

重点:动态路由方便,但生产环境更推荐“明确配置 + 可审计”的方式。

七、统一鉴权、限流与跨域

7.1 网关统一鉴权思路

网关统一鉴权的好处:

  • 不用每个服务重复写 Token 校验
  • 权限策略统一
  • 更适合做登录态拦截

常见做法:

  1. 请求先到 Gateway
  2. Gateway 校验 Token
  3. 校验通过后转发到业务服务
  4. 校验失败直接返回

7.2 认证信息透传到下游的整体流程

在实际项目里,网关通常不仅要做 Token 校验,还要把解析出来的用户身份传给下游服务。

典型流程:

text
客户端携带 Token -> Gateway 校验 Token
                 -> Gateway 解析出用户身份
                 -> Gateway 把身份信息写入请求头
                 -> 下游服务拦截器读取请求头
                 -> 下游服务放入 ThreadLocal
                 -> Controller / Service 获取当前登录用户

重点:网关负责“认证和透传”,下游服务负责“读取和消费认证信息”。

7.3 网关认证过滤器完整示例

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

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

        // 登录、注册等白名单接口直接放行
        if (path.startsWith("/auth/login") || path.startsWith("/auth/register")) {
            return chain.filter(exchange);
        }

        String token = request.getHeaders().getFirst("token");
        if (token == null || token.isBlank()) {
            return unauthorized(exchange, "token 不能为空");
        }

        try {
            // 这里可以替换成项目里的 JWT 工具类
            Long userId = JwtUtil.getUserId(token);
            String username = JwtUtil.getUsername(token);

            // 将认证信息写入请求头,透传给下游服务
            ServerHttpRequest newRequest = request.mutate()
                    .header("x-user-id", String.valueOf(userId))
                    .header("x-username", username)
                    .build();

            return chain.filter(exchange.mutate().request(newRequest).build());
        } catch (Exception ex) {
            return unauthorized(exchange, "token 非法或已过期");
        }
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");

        String body = "{\"code\":401,\"message\":\"" + message + "\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        // 鉴权过滤器尽量前置执行
        return 0;
    }
}

7.4 JWT 工具类示例

java
public class JwtUtil {

    // 实际项目中请放到配置文件中,并做好保密
    private static final String SECRET = "demo-secret-key";

    public static Long getUserId(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET.getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
        return Long.valueOf(claims.get("userId").toString());
    }

    public static String getUsername(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET.getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
        return claims.get("username").toString();
    }
}

7.5 认证信息请求头设计建议

推荐统一使用自定义请求头向下游透传身份信息,例如:

  • x-user-id
  • x-username
  • x-user-role
  • x-trace-id

使用建议:

  • 不要把原始敏感 Token 到处透传
  • 优先透传解析后的最小必要身份信息
  • 请求头命名统一,便于下游拦截器读取

7.6 下游服务通过拦截器读取认证信息

如果下游服务是 Spring MVC 项目,通常可以通过 HandlerInterceptor 读取网关透传的请求头。

java
@Data
@AllArgsConstructor
public class LoginUser {
    private Long userId;
    private String username;
}
java
public class UserContextHolder {

    private static final ThreadLocal<LoginUser> THREAD_LOCAL = new ThreadLocal<>();

    public static void set(LoginUser loginUser) {
        THREAD_LOCAL.set(loginUser);
    }

    public static LoginUser get() {
        return THREAD_LOCAL.get();
    }

    public static void clear() {
        THREAD_LOCAL.remove();
    }
}
java
@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String userId = request.getHeader("x-user-id");
        String username = request.getHeader("x-username");

        // 没有身份信息时,直接按未登录处理
        if (userId == null || username == null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 放入 ThreadLocal,供当前请求链路使用
        UserContextHolder.set(new LoginUser(Long.valueOf(userId), username));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求结束后务必清理,防止线程复用导致用户信息串号
        UserContextHolder.clear();
    }
}
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Resource
    private LoginUserInterceptor loginUserInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginUserInterceptor)
                // 登录接口本身一般不拦截
                .excludePathPatterns("/auth/login");
    }
}

7.7 下游业务代码获取当前登录用户

java
@RestController
@RequestMapping("/user")
public class CurrentUserController {

    @GetMapping("/current")
    public LoginUser currentUser() {
        // 直接从 ThreadLocal 中获取当前请求用户
        return UserContextHolder.get();
    }
}
java
@Service
public class OrderService {

    public String createOrder() {
        LoginUser loginUser = UserContextHolder.get();
        if (loginUser == null) {
            throw new RuntimeException("当前用户未登录");
        }

        // 在业务代码中直接使用当前登录用户信息
        return "create order by userId = " + loginUser.getUserId();
    }
}

7.8 关于 ThreadLocal 的重要说明

ThreadLocal 方案在 Spring MVC 下游服务中非常常见,但需要注意:

  • 必须在请求结束后执行 remove()
  • 只适合当前线程内共享数据
  • 异步线程、线程池任务默认拿不到原线程数据
  • 如果下游也是响应式 WebFlux,就不推荐继续使用传统 ThreadLocal

重点:ThreadLocal 很方便,但如果忘了清理,就非常容易引发用户身份串号问题。

7.9 跨域配置示例

yaml
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            # 允许跨域的来源
            allowedOriginPatterns: "*"
            # 允许的请求方式
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
            # 允许携带的请求头
            allowedHeaders: "*"
            # 是否允许携带 Cookie
            allowCredentials: true

7.10 基于 RedisRateLimiter 的限流示例

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: pay-route
          uri: lb://pay-service
          predicates:
            - Path=/pay/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒补充速率
                redis-rate-limiter.replenishRate: 5
                # 令牌桶容量
                redis-rate-limiter.burstCapacity: 10
                # 使用哪个 Bean 作为限流 Key 解析器
                key-resolver: "#{@ipKeyResolver}"

7.11 KeyResolver 示例

java
@Configuration
public class RateLimiterConfig {

    @Bean
    public KeyResolver ipKeyResolver() {
        // 按请求 IP 做限流
        return exchange -> Mono.just(
                Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress()
        );
    }
}

7.12 网关治理学习重点

  1. 统一鉴权
  2. 认证信息透传
  3. 下游用户上下文获取
  4. 统一跨域
  5. 统一限流
  6. 统一黑白名单
  7. 统一日志与链路标记

八、生产实践与性能优化

8.1 生产环境常见部署方式

text
客户端 -> Nginx / SLB -> Gateway 集群 -> 微服务集群

8.2 生产环境注意事项

  • Gateway 至少部署 2 个实例
  • 配合 Nacos 做服务发现
  • 配合 Redis 做限流
  • 配合 Sentinel 做熔断保护
  • 日志和追踪信息要统一

8.3 性能优化建议

  • 不要在过滤器里做重 CPU 操作
  • 不要在过滤器里做阻塞式远程调用
  • 路由规则要保持清晰
  • 鉴权逻辑尽量简洁
  • 统一超时设置

8.4 常见风险点

8.4.1 路由冲突

  • 多条 Path 规则重叠

8.4.2 过滤器顺序错误

  • 可能导致鉴权、日志、限流逻辑失效

8.4.3 网关单点故障

  • 需要集群和前置负载均衡

8.4.4 阻塞式代码拖慢网关

  • Gateway 基于 WebFlux,尽量避免阻塞调用

重点:Gateway 最怕的不是“配不出来”,而是“在高并发下被阻塞代码拖垮”。

九、常见面试题与故障排查

9.1 Gateway 的核心作用是什么

  • 统一入口
  • 路由转发
  • 鉴权
  • 限流
  • 统一治理

9.2 Gateway 和 Nginx 的区别是什么

  • Nginx 更偏基础设施层代理
  • Gateway 更偏微服务治理层

9.3 Gateway 和 Zuul 的区别是什么

  • Gateway 基于 WebFlux,性能更高
  • Gateway 更适合新一代 Spring Cloud 项目

9.4 路由不生效怎么办

重点排查:

  1. Path 是否匹配
  2. 服务名是否正确
  3. Nacos 是否注册成功
  4. uri 是否写成 lb://
  5. 过滤器是否提前拦截了请求

9.5 网关 404 怎么排查

  • 检查请求路径
  • 检查路由断言
  • 检查服务是否存在
  • 检查 StripPrefix 是否配置错误

9.6 鉴权过滤器不生效怎么办

  • 检查过滤器是否被 Spring 管理
  • 检查 getOrder() 顺序
  • 检查是否有其他过滤器提前返回

9.7 网关限流不生效怎么办

  • 检查 Redis 是否正常
  • 检查 RequestRateLimiter 配置
  • 检查 KeyResolver Bean 是否注册成功

9.8 高频面试重点

  1. Gateway 的核心概念
  2. Route、Predicate、Filter 区别
  3. 全局过滤器和局部过滤器
  4. Gateway 与 Nacos 的整合
  5. Gateway 限流和鉴权方案

十、最佳实践

10.1 路由设计建议

  • 一个业务域一组清晰路由
  • 不要把所有规则堆到一处
  • 核心路由显式配置

10.2 过滤器设计建议

  • 鉴权放前面
  • 日志紧随其后
  • 限流尽量前置
  • 不要在过滤器里写复杂业务逻辑

10.3 安全建议

  • 网关统一鉴权
  • 屏蔽内部服务直接暴露
  • 对敏感接口做限流
  • 做好黑白名单和签名校验

10.4 可维护性建议

  • 路由命名统一
  • 过滤器职责单一
  • 所有公共治理逻辑沉淀到网关层
  • 配置支持动态调整和审计

10.5 总结

Gateway 是微服务架构中非常核心的入口组件。

  • 入门阶段要掌握路由、断言、过滤器
  • 进阶阶段要掌握鉴权、跨域、限流、Nacos 整合
  • 高阶阶段要掌握集群部署、性能优化和生产治理

把这些能力串起来之后,Gateway 才真正能承担系统统一入口的角色。