Appearance
Spring Cloud Gateway
导航目录
- 一、Gateway 概述
- 二、为什么需要网关
- 三、Gateway 核心概念
- 四、路由配置与断言
- 五、过滤器机制
- 六、与 Nacos 整合实现动态路由
- 七、统一鉴权、限流与跨域
- 八、生产实践与性能优化
- 九、常见面试题与故障排查
- 十、最佳实践
一、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 的区别
| 对比项 | Gateway | Zuul 1.x |
|---|---|---|
| 底层模型 | WebFlux / Reactor | Servlet |
| 性能 | 更高 | 相对较低 |
| 编程模型 | 响应式 | 阻塞式 |
| 与 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 中最核心的概念,一条路由通常包含:
iduripredicatesfilters
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,POST4.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, gateway5.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 过滤器学习重点
- 局部过滤器和全局过滤器区别
- 过滤器执行顺序
- 请求前置处理和响应后置处理
- 鉴权过滤器
- 日志过滤器
六、与 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: true6.3 动态路由说明
开启自动发现后:
user-service可通过/user-service/**访问order-service可通过/order-service/**访问
6.4 动态路由的使用建议
- 学习阶段可以开启,方便快速验证
- 生产环境通常仍建议显式配置核心路由
重点:动态路由方便,但生产环境更推荐“明确配置 + 可审计”的方式。
七、统一鉴权、限流与跨域
7.1 网关统一鉴权思路
网关统一鉴权的好处:
- 不用每个服务重复写 Token 校验
- 权限策略统一
- 更适合做登录态拦截
常见做法:
- 请求先到 Gateway
- Gateway 校验 Token
- 校验通过后转发到业务服务
- 校验失败直接返回
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-idx-usernamex-user-rolex-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: true7.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 网关治理学习重点
- 统一鉴权
- 认证信息透传
- 下游用户上下文获取
- 统一跨域
- 统一限流
- 统一黑白名单
- 统一日志与链路标记
八、生产实践与性能优化
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 路由不生效怎么办
重点排查:
Path是否匹配- 服务名是否正确
- Nacos 是否注册成功
uri是否写成lb://- 过滤器是否提前拦截了请求
9.5 网关 404 怎么排查
- 检查请求路径
- 检查路由断言
- 检查服务是否存在
- 检查 StripPrefix 是否配置错误
9.6 鉴权过滤器不生效怎么办
- 检查过滤器是否被 Spring 管理
- 检查
getOrder()顺序 - 检查是否有其他过滤器提前返回
9.7 网关限流不生效怎么办
- 检查 Redis 是否正常
- 检查
RequestRateLimiter配置 - 检查
KeyResolverBean 是否注册成功
9.8 高频面试重点
- Gateway 的核心概念
- Route、Predicate、Filter 区别
- 全局过滤器和局部过滤器
- Gateway 与 Nacos 的整合
- Gateway 限流和鉴权方案
十、最佳实践
10.1 路由设计建议
- 一个业务域一组清晰路由
- 不要把所有规则堆到一处
- 核心路由显式配置
10.2 过滤器设计建议
- 鉴权放前面
- 日志紧随其后
- 限流尽量前置
- 不要在过滤器里写复杂业务逻辑
10.3 安全建议
- 网关统一鉴权
- 屏蔽内部服务直接暴露
- 对敏感接口做限流
- 做好黑白名单和签名校验
10.4 可维护性建议
- 路由命名统一
- 过滤器职责单一
- 所有公共治理逻辑沉淀到网关层
- 配置支持动态调整和审计
10.5 总结
Gateway 是微服务架构中非常核心的入口组件。
- 入门阶段要掌握路由、断言、过滤器
- 进阶阶段要掌握鉴权、跨域、限流、Nacos 整合
- 高阶阶段要掌握集群部署、性能优化和生产治理
把这些能力串起来之后,Gateway 才真正能承担系统统一入口的角色。