1. Spring Cloud Gateway 是什么?
Spring Cloud Gateway 是 Spring Cloud 生态系统中的现代化 API 网关组件,用于构建微服务架构中的统一入口网关。它基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.x 构建,采用响应式编程模型,提供高性能、非阻塞式的 API 路由和横切关注点处理能力。
1.1 技术架构基础
- 响应式编程:基于 Project Reactor 的响应式流处理
- WebFlux:使用 Spring WebFlux 而非传统的 Servlet 模型
- 函数式编程:支持 Java 8+ 的函数式编程风格
- 高性能:相比 Zuul 1.x(阻塞式),性能提升显著
1.2 在微服务架构中的定位
客户端 → Spring Cloud Gateway → 微服务A
→ 微服务B
→ 微服务C
作为所有微服务的统一入口点,客户端只需要与网关交互,无需知道后端服务的具体位置。
2. Spring Cloud Gateway 的作用
2.1 核心作用
- 统一入口:为所有微服务提供单一访问入口
- 路由转发:根据配置规则将请求路由到对应的服务
- 负载均衡:集成 Spring Cloud LoadBalancer 实现服务发现和负载均衡
- 协议适配:支持 HTTP/HTTPS、WebSocket 等协议
- 监控指标:收集请求日志、性能指标等监控数据
2.2 横切关注点处理
- 认证(Authentication):统一处理用户身份验证
- 安全防护:防重放攻击、IP 黑白名单、基础安全头设置
- 限流熔断:实现请求限流、服务降级等保护机制
- 请求/响应修改:修改请求头、响应头等
3. Spring Cloud Gateway 的特点
3.1 技术特点
- 响应式非阻塞:基于 Reactor 的异步非阻塞处理模型
- 高性能:单机可处理数万 QPS
- 灵活路由:支持多种路由匹配方式(Path、Host、Method、Header 等)
- 丰富过滤器:内置多种过滤器,支持自定义过滤器
- 动态配置:支持运行时动态修改路由配置
- 服务发现集成:无缝集成 Eureka、Consul、Nacos 等注册中心
3.2 功能组件
- Route(路由):定义请求如何被转发到目标服务
- Predicate(断言):匹配 HTTP 请求的各种条件
- Filter(过滤器):修改请求和响应的逻辑
- GlobalFilter(全局过滤器):应用于所有路由的过滤器
4. 网关应该做什么 vs 不应该做什么
4.1 ✅ 网关应该做的事情
4.1.1 路由和转发
- 根据路径、主机名等条件路由请求
- 实现负载均衡和服务发现
- 处理协议转换(如 WebSocket)
4.1.2 基础安全(认证层面)
- 身份认证:验证 JWT Token、API Key 等的有效性
- Token 验证:检查签名、过期时间、基本格式
- 安全头处理:添加 X-Forwarded-For、X-Real-IP 等头信息
- 基础防护:IP 黑白名单、防刷、防重放攻击
4.1.3 运维和监控
- 统一日志:记录访问日志、请求响应时间
- 指标收集:收集 QPS、响应时间、错误率等指标
- 健康检查:提供网关自身的健康检查端点
- 限流熔断:基于 Redis 或内存的请求限流
4.1.4 请求/响应处理
- 路径重写:StripPrefix、PrefixPath 等
- 头信息修改:添加、删除、修改请求/响应头
- 重试机制:对失败请求进行重试
4.2 ❌ 网关不应该做的事情
4.2.1 业务权限控制(授权)
- 不应该进行细粒度权限验证(如:用户A能否访问用户B的数据)
- 不应该验证业务级别的权限(如:是否有删除权限、编辑权限)
- 不应该替代业务服务的安全逻辑
4.2.2 业务逻辑处理
- 不应该包含业务逻辑(如:订单状态验证、库存检查)
- 不应该调用业务数据库进行复杂查询
- 不应该处理业务数据转换
4.2.3 复杂数据处理
- 不应该解析和验证复杂的请求体
- 不应该进行业务数据的校验和转换
- 不应该缓存业务数据
4.3 🏗️ 正确的安全分层架构
客户端 → 网关层(Authentication) → 业务服务层(Authorization)
↓ ↓
验证"你是谁" 验证"你能做什么"
Token有效性验证 业务权限验证
基础安全防护 数据权限控制
5. 详细使用示例
5.1 基础环境搭建
5.1.1 Maven 依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-gateway-demo</artifactId>
<version>1.0.0</version>
<name>spring-cloud-gateway-demo</name>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务发现客户端(用于集成注册中心) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Actuator 监控端点 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Redis 依赖(用于限流) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
5.1.2 基础配置文件 (application.yml)
server:
port: 9000 # 网关服务端口
spring:
application:
name: api-gateway # 应用名称
cloud:
gateway:
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*" # 允许所有源
allowedMethods: "*" # 允许所有HTTP方法
allowedHeaders: "*" # 允许所有请求头
allowCredentials: true # 允许携带凭证
# 路由配置
routes:
# 用户服务路由 - 网关只负责路由和基础认证
- id: user-service-route
uri: http://localhost:8081
predicates:
- Path=/api/users/** # 匹配用户相关路径
filters:
- StripPrefix=2 # 去掉 /api/users 前缀,实际转发到 /**
# 订单服务路由
- id: order-service-route
uri: http://localhost:8082
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=2
# Actuator 监控配置
management:
endpoints:
web:
exposure:
include: '*' # 暴露所有监控端点
endpoint:
gateway:
enabled: true # 启用网关管理端点
5.2 路由配置详解
5.2.1 Java 代码配置路由
package com.example.gateway.config;
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;
/**
* 网关路由配置类
* 使用 Java 代码方式配置路由,相比配置文件更加灵活
* 注意:这里只配置路由规则,不包含业务权限逻辑
*/
@Configuration
public class GatewayRouteConfig {
/**
* 配置自定义路由规则
* @param builder RouteLocatorBuilder 用于构建路由
* @return RouteLocator 路由定位器
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service", r -> r
.path("/api/users/**") // 匹配路径
.uri("http://localhost:8081") // 目标服务地址
.filters(f -> f.stripPrefix(2)) // 去掉前两个路径段 (/api/users)
)
// 订单服务路由
.route("order-service", r -> r
.path("/api/orders/**")
.uri("http://localhost:8082")
.filters(f -> f.stripPrefix(2))
)
// 公开API路由(无需认证)
.route("public-api", r -> r
.path("/api/public/**")
.uri("http://localhost:8083")
.filters(f -> f.stripPrefix(2))
)
.build();
}
}
5.3 断言(Predicates)使用示例
package com.example.gateway.config;
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 PredicateConfig {
@Bean
public RouteLocator predicateRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 1. Path 断言:基于URL路径匹配
.route("path-route", r -> r
.path("/api/v1/**", "/api/v2/**") // 支持多个路径模式
.uri("http://localhost:8081")
)
// 2. Method 断言:基于HTTP方法匹配
.route("method-route", r -> r
.path("/api/method/**")
.and()
.method("GET", "POST") // 只匹配GET和POST请求
.uri("http://localhost:8082")
)
// 3. Header 断言:基于请求头匹配
.route("header-route", r -> r
.path("/api/header/**")
.and()
.header("X-API-Version", "v1") // 必须包含指定请求头
.uri("http://localhost:8083")
)
// 4. Query 断言:基于查询参数匹配
.route("query-route", r -> r
.path("/api/query/**")
.and()
.query("version", "1.0") // 查询参数 version=1.0
.uri("http://localhost:8084")
)
// 5. Host 断言:基于Host头匹配
.route("host-route", r -> r
.host("api.example.com", "api.test.com") // 匹配指定域名
.uri("http://localhost:8085")
)
.build();
}
}
5.4 过滤器(Filters)使用示例
5.4.1 内置过滤器配置
package com.example.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
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 FilterConfig {
@Bean
public RouteLocator filterRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 1. 添加请求头过滤器
.route("add-header-route", r -> r
.path("/api/add-header/**")
.filters(f -> f
// 添加网关来源标识
.addRequestHeader("X-Gateway-Source", "spring-cloud-gateway")
// 添加请求时间戳
.addRequestHeader("X-Request-Timestamp",
String.valueOf(System.currentTimeMillis()))
)
.uri("http://localhost:8081")
)
// 2. 添加响应头过滤器
.route("add-response-header-route", r -> r
.path("/api/add-response-header/**")
.filters(f -> f
.addResponseHeader("X-Gateway-Processed", "true")
)
.uri("http://localhost:8082")
)
// 3. 路径重写过滤器
.route("strip-prefix-route", r -> r
.path("/api/strip/**")
.filters(f -> f.stripPrefix(1)) // 去掉第一个路径段
.uri("http://localhost:8083")
)
// 4. 限流过滤器(使用Redis)
.route("rate-limiter-route", r -> r
.path("/api/rate-limiter/**")
.filters(f -> f
.requestRateLimiter()
.rateLimiter(RedisRateLimiter.class)
// 配置限流参数:每秒5个请求,突发容量10个
.configure(c -> c.setBurstCapacity(10).setReplenishRate(5))
)
.uri("http://localhost:8084")
)
// 5. 重试过滤器
.route("retry-route", r -> r
.path("/api/retry/**")
.filters(f -> f.retry(3)) // 失败时重试3次
.uri("http://localhost:8085")
)
.build();
}
}
5.5 网关安全配置(正确的做法)
5.5.1 网关层认证过滤器(只做身份认证)
package com.example.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* 网关认证过滤器 - 只负责身份认证,不负责权限授权
*
* 职责范围:
* ✅ 验证JWT Token的有效性(签名、过期时间)
* ✅ 验证Token的基本格式
* ✅ 将用户信息传递给下游服务
* ❌ 不进行业务权限验证
* ❌ 不调用业务数据库
* ❌ 不处理复杂的业务逻辑
*/
@Component
public class AuthenticationGlobalFilter implements GlobalFilter, Ordered {
// 公开路径列表(无需认证的路径)
private static final List<String> PUBLIC_PATHS = Arrays.asList(
"/api/public/**",
"/api/auth/login",
"/api/auth/register",
"/actuator/**"
);
/**
* 全局过滤器执行逻辑
* @param exchange 当前请求交换对象
* @param chain 过滤器链
* @return Mono<Void> 响应式返回
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestPath = exchange.getRequest().getURI().getPath();
// 1. 检查是否为公开路径,如果是则直接放行
if (isPublicPath(requestPath)) {
return chain.filter(exchange);
}
// 2. 从请求头中提取认证令牌
String token = extractToken(exchange);
if (token == null || token.isEmpty()) {
return handleUnauthorized(exchange, "Missing authentication token");
}
try {
// 3. 验证令牌有效性(只验证签名和过期时间,不验证业务权限)
if (!validateTokenSignatureAndExpiry(token)) {
return handleUnauthorized(exchange, "Invalid or expired token");
}
// 4. 从令牌中提取用户基本信息
String userId = extractUserIdFromToken(token);
String userRoles = extractUserRolesFromToken(token);
// 5. 将用户信息添加到请求头,传递给下游业务服务
// 下游服务将基于这些信息进行具体的权限验证
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-User-Id", userId)
.header("X-User-Roles", userRoles)
.header("X-Authenticated", "true")
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (Exception e) {
return handleUnauthorized(exchange, "Authentication processing failed: " + e.getMessage());
}
}
/**
* 判断路径是否为公开路径
* @param path 请求路径
* @return 是否为公开路径
*/
private boolean isPublicPath(String path) {
return PUBLIC_PATHS.stream()
.anyMatch(publicPath -> path.matches(publicPath.replace("**", ".*")));
}
/**
* 从请求中提取认证令牌
* 支持 Bearer Token 和自定义 Header
*/
private String extractToken(ServerWebExchange exchange) {
// 优先从 Authorization 头获取
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7); // 去掉 "Bearer " 前缀
}
// 也可以从自定义头或查询参数获取
String tokenHeader = exchange.getRequest().getHeaders().getFirst("X-API-Token");
if (tokenHeader != null) {
return tokenHeader;
}
return null;
}
/**
* 验证令牌签名和过期时间
* 注意:这里只做基础验证,不做业务权限验证
*/
private boolean validateTokenSignatureAndExpiry(String token) {
// 实际项目中应该使用 JWT 库进行验证
// 这里简化处理,实际应该验证签名、过期时间等
try {
// 伪代码:验证 JWT 签名和过期时间
// Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return token.length() > 20; // 简单验证
} catch (Exception e) {
return false;
}
}
/**
* 从令牌中提取用户ID
* 实际项目中应该解析 JWT payload
*/
private String extractUserIdFromToken(String token) {
// 伪代码:从 JWT 中提取用户ID
return "user123"; // 简化处理
}
/**
* 从令牌中提取用户角色
* 实际项目中应该解析 JWT payload 中的角色信息
*/
private String extractUserRolesFromToken(String token) {
// 伪代码:从 JWT 中提取角色
return "USER,PREMIUM"; // 简化处理
}
/**
* 处理未授权请求
*/
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
String response = "{\"error\":\"Unauthorized\",\"message\":\"" + message + "\"}";
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(response.getBytes()))
);
}
/**
* 设置过滤器执行顺序
* 数值越小,优先级越高
*/
@Override
public int getOrder() {
return -100; // 在很早的阶段执行认证
}
}
5.5.2 业务服务层权限控制(真正的授权)
// 用户服务中的权限控制示例
package com.example.userservice.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
/**
* 用户控制器 - 在业务服务中进行真正的权限验证
*
* 网关已经完成了身份认证,这里进行业务级别的权限授权
*/
@RestController
@RequestMapping("/api")
public class UserController {
/**
* 获取用户详情
* 需要验证当前用户是否有权限访问目标用户
*/
@GetMapping("/users/{userId}")
public User getUser(@RequestHeader("X-User-Id") String currentUserId,
@RequestHeader("X-User-Roles") String userRoles,
@PathVariable String userId) {
// 1. 验证是否是访问自己的信息(普通用户只能访问自己)
if (!currentUserId.equals(userId)) {
// 2. 如果不是访问自己,检查是否具有管理员角色
if (!userRoles.contains("ADMIN")) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
"Insufficient permissions to access this user data");
}
}
// 3. 执行业务逻辑
return userService.findById(userId);
}
/**
* 删除用户 - 需要管理员权限
* 使用 Spring Security 注解进行权限控制
*/
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{userId}")
public void deleteUser(@PathVariable String userId) {
userService.delete(userId);
}
}
5.6 日志和监控配置
5.6.1 访问日志过滤器
package com.example.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
/**
* 访问日志全局过滤器
* 记录请求的基本信息,用于监控和审计
* 注意:只记录基础信息,不记录敏感业务数据
*/
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 记录请求开始时间
long startTime = System.currentTimeMillis();
String path = exchange.getRequest().getURI().getPath();
String method = exchange.getRequest().getMethodValue();
String clientIp = getClientIp(exchange);
System.out.println(String.format(
"[ACCESS-LOG] %s | %s | %s | %s",
LocalDateTime.now(), method, path, clientIp
));
// 继续处理请求,并记录响应时间
return chain.filter(exchange).doOnTerminate(() -> {
long duration = System.currentTimeMillis() - startTime;
System.out.println(String.format(
"[ACCESS-LOG] Response time: %d ms for %s", duration, path
));
});
}
private String getClientIp(ServerWebExchange exchange) {
String xForwardedFor = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1; // 在最后执行,确保能获取到完整信息
}
}
5.7 完整的启动类
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Cloud Gateway 网关启动类
*
* 设计原则:
* ✅ 网关只负责:路由转发、身份认证、基础安全、监控日志
* ❌ 网关不负责:业务权限、业务逻辑、数据验证
*
* 启动后可用端点:
* - 网关服务:http://localhost:9000
* - 路由信息:http://localhost:9000/actuator/gateway/routes
* - 健康检查:http://localhost:9000/actuator/health
*/
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
System.out.println("=====================================");
System.out.println("Spring Cloud Gateway 启动成功!");
System.out.println("网关地址: http://localhost:9000");
System.out.println("路由管理: http://localhost:9000/actuator/gateway/routes");
System.out.println("=====================================");
}
}
6. 最佳实践总结
6.1 网关职责边界
- 网关层:路由 + 认证 + 基础安全 + 监控
- 业务层:授权 + 业务逻辑 + 数据验证
6.2 性能优化建议
- 合理配置线程池和连接池
- 避免在过滤器中执行耗时操作
- 使用缓存减少重复计算
6.3 安全最佳实践
- 网关只做身份认证,不做权限授权
- 敏感信息不要在网关层处理
- 使用 HTTPS 保护传输安全
6.4 监控和运维
- 集成 Prometheus + Grafana
- 配置合理的健康检查
- 记录详细的访问日志
这份文档详细说明了 Spring Cloud Gateway 的正确使用方式,特别强调了网关的职责边界,避免了常见的架构设计误区。所有示例都包含详细的中文注释,帮助您理解每个组件的正确用途和实现方式。
2244

被折叠的 条评论
为什么被折叠?



