SpringCloud-网关

网关

纵向拆分 (Vertical Splitting / Business Domain Splitting): 关注点是业务功能完整性。每个服务是一个独立的业务单元,例如:用户服务、商品服务、订单服务、支付服务。它们通常有自己的数据库。

水平拆分 (Horizontal Splitting / Cross-cutting Concern Splitting): 关注点是通用能力或技术功能。这些功能被多个业务服务共享,例如:认证授权服务、日志服务、配置服务、消息队列服务、文件存储服务。它们通常是无状态的或有自己的独立数据存储。

API 网关 (API Gateway): 它是微服务架构中的一个关键组件,扮演着门面 (Facade) 的角色。它不仅处理横切关注点(如认证、限流),更重要的是提供统一的入口和路由,将内部复杂的微服务结构对外部客户端透明化,从而简化客户端与微服务系统的交互。


SpringCloudGateway

相比于Netfilx的Zuul,SpringCloudGateway更好用,也更受欢迎

导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependencies>
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

配置路由规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: item-service
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
……

路由断言

Spring提供了多种基本的RoutePredicateFactory

路由断言的模板使用方法

名称 说明
After 是某个时间点后的请求
Before 是某个时间点之前的请求
Between 是某两个时间点之前的请求
Cookie 请求必须包含某些cookie
Header 请求必须包含某些header
Host 请求必须是访问某个host (域名)
Method 请求方式必须是指定方式
Path 请求路径必须符合指定规则
Query 请求参数必须包含指定参数
RemoteAddr 请求者的ip必须是指定范围
Weight 权重处理
XForwarded Remote Addr 基于请求的来源IP做判断

路由过滤器

路由过滤器有更多中配置的方式,在此处我们列出常用的路由配置

同样的,我们也可以去官网找到配置说明和样板代码

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除一个响应头
RewritePath 请求路径重写
StripPrefix 去除请求路径中的N段前缀

如果想要该过滤器对所有的路由都生效,则需要配置默认过滤器

1
2
3
4
5
gateway:
routes:
……
default-filters:
- AddResponseHeader= truth, try

自定义过滤器

自定义过滤器也分为普通过滤器和全局过滤器

在这里,普通过滤器自定义起来很难,也很少有应用场景,所以大多使用全局过滤器

GlobalFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyGlobalFilter implements GlobalFilter, Ordered {
/**
* 实现全局过滤器接口的方法
*
* @param exchange 登陆上下文,用以获取参数
* @param chain 网关过滤器链,当前过滤器执行完之后放行
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 模拟登陆校验
return chain.filter(exchange);
}

/**
* 实现排序接口方法,设置过滤器优先级
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}

登陆校验

在网关处自定义一个全局过滤器,获取请求头中的path信息,比对排除地址,看是否需要校验token

从请求头中拿到token,调用JwtTool解析token,拿到userId放到请求头中向下传递并放行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* 全局认证过滤器。
* 负责在请求到达后端服务之前,进行JWT令牌的校验和用户身份的认证。
* 如果认证失败,则直接返回未授权响应。
*/
@Component // 标记为Spring组件,Spring容器会自动扫描并管理这个类的实例(Bean)
@RequiredArgsConstructor // Lombok注解:自动生成一个包含所有final字段的构造函数,用于依赖注入
public class AuthGlobalFilter implements GlobalFilter, Ordered {

// 认证相关的配置属性,例如需要排除认证的路径列表
private final AuthProperties authProperties;

// JWT工具类,用于JWT的创建、解析和验证
private final JwtTool jwtTool;

// Ant风格路径匹配器,用于判断请求路径是否匹配某个模式(例如排除路径)
// 这里直接实例化,因为它是一个无状态的工具类,不需要Spring容器管理其生命周期
private final AntPathMatcher antPathMatcher = new AntPathMatcher();

/**
* 定义过滤器的执行顺序。
* 数值越小,优先级越高,越早执行。
*
* @return 过滤器的顺序值。这里返回0,表示这是一个较早执行的过滤器。
*/
@Override
public int getOrder() {
return 0;
}

/**
* 核心过滤逻辑。
* 在每个请求通过网关时执行。
*
* @param exchange 包含请求和响应的Web交换对象。
* @param chain 过滤器链,用于将请求传递给下一个过滤器或目标服务。
* @return 一个Mono<Void>,表示过滤操作的完成。
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取当前请求对象
ServerHttpRequest request = exchange.getRequest();

// 2. 判断当前请求路径是否在排除认证的列表中
if (isExcludePath(request.getPath().toString())) {
// 如果是排除路径,则直接放行,不进行后续的认证逻辑
return chain.filter(exchange);
}

// 3. 从请求头中获取Authorization(授权)令牌
String token = null;
List<String> authorization = request.getHeaders().get("Authorization");
if (authorization != null && !authorization.isEmpty()) {
token = authorization.get(0); // 通常Authorization头只有一个值
// 注意:如果token是"Bearer xxx"格式,JwtTool需要能处理或在此处剥离"Bearer "前缀
// 你的JwtTool目前直接处理整个字符串,所以如果前端发送"Bearer xxx",JwtTool需要能识别
// 假设JwtTool能够处理或期望原始token,或者前端不带"Bearer "
}

// 4. 解析并验证JWT令牌,获取用户ID
Long userId = null;
try {
userId = jwtTool.parseToken(token); // 调用JwtTool解析token
// 成功解析后,可以将userId存储到exchange的属性中,以便后续服务获取
// 例如:exchange.getAttributes().put("userId", userId);
} catch (UnauthorizedException e) {
// 如果解析token过程中抛出UnauthorizedException(如token为空、无效、过期)
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 设置HTTP状态码为401未授权
return response.setComplete(); // 终止请求,返回响应
}

// TODO: 传递用户信息到下游服务

// 5. 如果认证成功,则将请求传递给过滤器链中的下一个过滤器或最终的目标服务
return chain.filter(exchange);
}

/**
* 判断给定的路径是否在认证排除列表中。
*
* @param path 待检查的请求路径。
* @return 如果路径匹配任何一个排除模式,则返回true;否则返回false。
*/
private boolean isExcludePath(String path) {
// 遍历配置中所有需要排除认证的路径模式
for (String pathPattern : authProperties.getExcludePaths()){
// 使用AntPathMatcher进行路径匹配
if (antPathMatcher.match(pathPattern, path)) {
return true; // 如果匹配成功,说明该路径需要排除认证
}
}
return false; // 所有模式都不匹配,表示该路径需要认证
}
}

UserId

在微服务中,我们可以设置MVC的过滤器,拿到请求头的userId并存放到ThreadLocal中

设置请求头

1
2
3
4
5
6
// 传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate().request(
builder -> builder
.header("user-info", userInfo)
).build();

获取请求头

每一个微服务都需要使用拦截器获取到请求头中的userId,所以我们将获取逻辑抽取到Common库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的 token
String UserInfo = request.getHeader("User-Info");
if (StrUtil.isBlank(UserInfo)) {
// 2.存入上下文
UserContext.setUser(Long.valueOf(UserInfo));
}
// 3.放行
return true;
}
}

别忘了要在common中新建一个配置类

1
2
3
4
5
6
7
8
@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 只有SpringMVC才加载此配置类,防止网关加载
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}

spring.factories

在 Spring Boot 微服务架构中,当 common 模块作为共享库被其他微服务依赖时,common 模块中定义的配置类在微服务启动时不会被自动装配。

Spring Boot 默认的组件扫描机制仅会扫描主应用类所在的包及其子包。如果 common 模块的配置类位于不同的根包下,或不在主应用类的扫描路径内,它们将无法被 Spring Boot 容器发现和加载。

为了使 common 模块的配置类能够被引用它的微服务自动加载,我们应利用 Spring Boot 的自动配置机制

在 common 模块的 src/main/resources/META-INF/ 目录下创建 spring.factories 文件

1
2
3
4
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig,\
com.hmall.common.config.JsonConfig

OpenFeign传递UserInfo

OpenFeign服务间调用并不会走Mvc,也就不会走Mvc的过滤器(也就是提取UserInfo)

那么在服务间调用应该如何传递UserInfo呢?

OpenFeign中提供了拦截器的接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor(){
@Override
public void apply(feign.RequestTemplate requestTemplate) {
Long userid = UserContext.getUser();
if (userid != null)
{
requestTemplate.header("user-id", userid.toString());
}
}
};
}

别忘了在启动类的注释中应用这个拦截器

1
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = FeignInterceptor.class)


SpringCloud-网关
http://blog.170827.xyz/2025/06/18/SpringCloud-网关/
作者
XIAOBAI
发布于
2025年6月18日
许可协议