文章目录
- 前言
- [一、Gateway 网关的搭建](#一、Gateway 网关的搭建)
-
- [1.1 创建 Gateway 模块](#1.1 创建 Gateway 模块)
- [1.2 引入依赖](#1.2 引入依赖)
- [1.3 配置网关](#1.3 配置网关)
- [1.4 验证网关是否搭建成功](#1.4 验证网关是否搭建成功)
- [1.5 微服务结构分析](#1.5 微服务结构分析)
- [二、Gateway 断言工厂](#二、Gateway 断言工厂)
-
- [2.1 Spring 提供的断言工厂](#2.1 Spring 提供的断言工厂)
- [2.2 示例:设置断言工厂](#2.2 示例:设置断言工厂)
- [三、Gateway 路由过滤器及其工厂](#三、Gateway 路由过滤器及其工厂)
-
- [3.1 路由过滤器 GatewayFilter](#3.1 路由过滤器 GatewayFilter)
- [3.2 路由过滤器工厂 GatewayFilter Factory](#3.2 路由过滤器工厂 GatewayFilter Factory)
- [3.3 示例:添加过滤器工厂](#3.3 示例:添加过滤器工厂)
- [3.4 默认过滤器](#3.4 默认过滤器)
- [四、Gateway 全局过滤器](#四、Gateway 全局过滤器)
-
- [4.1 全局过滤器的概念和作用](#4.1 全局过滤器的概念和作用)
- [4.2 GlobalFilter 接口定义](#4.2 GlobalFilter 接口定义)
- [4.3 示例:定义全局过滤器进行用户身份验证](#4.3 示例:定义全局过滤器进行用户身份验证)
- 五、过滤器链的执行顺序
-
- [5.1 过滤器链执行过程](#5.1 过滤器链执行过程)
- [5.2 过滤器的排序规则](#5.2 过滤器的排序规则)
- [5.3 为什么三种不同的过滤器可以进行排序形成过滤器链](#5.3 为什么三种不同的过滤器可以进行排序形成过滤器链)
- 六、跨域问题
-
- [6.1 什么是跨越问题](#6.1 什么是跨越问题)
- [6.2 演示跨越问题](#6.2 演示跨越问题)
- [6.3 使用 Gateway 解决跨域问题](#6.3 使用 Gateway 解决跨域问题)
- [6.3 使用 Gateway 解决跨域问题](#6.3 使用 Gateway 解决跨域问题)
前言
为什么需要网关以及网关的作用
在微服务架构中,网关是至关重要的组件,具有多重职责,为整个系统提供了一系列关键功能。从下面的微服务结构图中,我们可以明确网关的几项主要作用:
微服务结构图:
-
请求过滤与安全:
用户的所有请求首先经过网关,这使得网关成为系统的第一道防线。通过对传入请求的过滤、验证和安全策略的实施,网关确保只有合法的请求能够访问内部的微服务。
-
路由与负载均衡:
网关具有路由功能,能够将收到的请求正确地分发给相应的微服务。这种路由机制使得系统更加灵活,同时,网关还结合了负载均衡的特性,确保各个微服务能够平衡地处理请求,提升系统的性能和可用性。
-
统一的接入点:
作为系统的入口,网关提供了一个统一的接入点,简化了客户端与微服务之间的通信。客户端只需与网关进行交互,而无需直接与各个微服务打交道,从而降低了系统的复杂性。
-
请求转换与聚合:
在实际应用中,某些信息可能分布在多个微服务中。网关的聚合和转换功能使得它能够从多个微服务中收集数据,然后以符合客户端期望的格式返回。此外,网关还可以处理请求和响应的转换,以适应不同微服务的接口。
-
请求限流与熔断:
网关在系统入口处实施请求限流和熔断机制,以防止不良请求对整个系统造成影响。通过在网关层面进行控制,系统能够有效地抵御过载和故障,提高整体的稳定性。
综合而言,网关在微服务架构中扮演了关键角色,通过提供统一入口、安全性、路由、负载均衡等功能,为整个系统的可维护性、可伸缩性和可用性奠定了基础。
网关的技术实现
在Spring Cloud中,实现网关的两种主要方式是使用Zuul
和Gateway
。下面简要介绍它们的特点和区别:
1. Zuul
Zuul是一个基于Servlet的网关实现,它在Spring Cloud中充当了路由和过滤器的角色。主要特点包括:
-
阻塞式编程: Zuul采用阻塞式的处理方式,即每个请求都会在一个单独的线程中处理,等待响应完成后才能继续处理其他请求。
-
功能全面: Zuul不仅提供了路由功能,还支持请求的过滤、认证、授权等多种功能。这使得它成为一个功能较为完备的网关方案。
2. Spring Cloud Gateway
Spring Cloud Gateway是基于 Spring 5 中引入的 WebFlux 框架的响应式编程实现。与 Zuul 相比,它具有以下特点:
-
响应式编程: Gateway 采用响应式编程模型,利用反应式流处理请求。这使得它能够更高效地处理大量并发请求,具备更好的性能。
-
简化的过滤器链: Gateway 引入了全局过滤器、路由断言和过滤器工厂等概念,使过滤器的配置更为灵活。相较于 Zuul,Gateway 提供了更清晰、简洁的过滤器链定义。
-
内置断言支持: Gateway 内置了多种路由断言,可以根据请求的各种属性进行路由,提供了更强大的路由功能。
-
动态路由: Gateway 支持动态路由配置,可以在运行时添加、删除路由规则,灵活适应微服务架构的变化。
总体而言,选择使用Zuul还是 Spring Cloud Gateway 取决于具体需求和项目的性能要求。Zuul 在功能上较为全面,而 Spring Cloud Gateway 则更适用于对性能和响应式编程有较高要求的场景。
一、Gateway 网关的搭建
在理解了为什么需要网关以及网关的作用后,我们可以尝试在 cloud-demo
中搭建一个 Gateway 网关。
1.1 创建 Gateway 模块
首先,在项目中创建一个新的模块 gateway
,作为我们的网关服务。
然后,为Gateway 模块创建合适的包,并创建一个GatewayApplication
启动类:
java
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
1.2 引入依赖
在 pom.xml
文件中引入 Gateway 网关和 Nacos 注册中心的依赖。
xml
<!-- 网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务发现依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在上述 pom.xml
文件中你引入了 Spring Cloud Gateway 和 Nacos 服务发现的依赖。这两个依赖的作用:
-
spring-cloud-starter-gateway
: 这是 Spring Cloud Gateway 的启动器依赖,它包含了构建 Gateway 时所需的核心依赖和配置。 -
spring-cloud-starter-alibaba-nacos-discovery
: 这是阿里巴巴的 Nacos 服务发现的启动器依赖。它为项目提供了与 Nacos 服务注册和发现相关的功能。
通过这两个依赖,就可以在 Spring Cloud 中轻松搭建起一个网关服务,并使用 Nacos 作为服务注册与发现的工具。在微服务体系结构中,Nacos 的作用是集中管理和发现各个微服务,使它们能够相互协作。
1.3 配置网关
在 application.yml
配置文件中,编写网关的路由配置和 Nacos 的地址信息。
yml
server:
port: 10010 # 网关端口
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由 ID,自定义的唯一即可
# uri: http://127.0.0.1:8081 # 固定的地址
uri: lb://userservice # 路由的目标地址,其中 lb 代表 loadBalance 负载均衡,然后是服务的名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以"/user/"开头的请求就符合要求,即会路由到 userservice 服务
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
在上述 application.yml
中,配置了网关的路由规则和 Nacos 的地址信息。对这个配置文件各个部分的说明:
-
server.port
: 设置了网关服务的端口为10010
。 -
spring.application.name
: 将网关服务的应用名称设置为gateway
。 -
spring.cloud.nacos.server-addr
: 配置了 Nacos 注册中心的地址为localhost:8848
。 -
spring.cloud.gateway.routes
: 定义了两个网关路由规则。-
路由规则1 (
user-service
):id
: 路由的唯一标识,这里设置为user-service
。uri
: 设置了路由的目标地址为lb://userservice
,其中lb://
表示使用负载均衡,后面跟着服务的名称userservice
。predicates
: 路由断言,指定了只有请求路径以/user/
开头才会匹配到这个路由规则。
-
路由规则2 (
order-service
):id
: 路由的唯一标识,这里设置为order-service
。uri
: 设置了路由的目标地址为lb://orderservice
,同样使用了负载均衡,后面是服务的名称orderservice
。predicates
: 路由断言,指定了只有请求路径以/order/
开头才会匹配到这个路由规则。
-
这样配置的结果是,当请求进入网关时,如果路径以 /user/
开头,它将被路由到 userservice
服务;如果路径以 /order/
开头,它将被路由到 orderservice
服务。这样通过网关,可以在不暴露内部服务地址的情况下,统一管理和调度服务。
1.4 验证网关是否搭建成功
通过上述网关的搭建工作之后,我们可以启动 gateway
服务,然后在浏览器中访问 10010 端口,查看是否能够成功访问 userservice
和 orderservice
服务:
此时发现分别访问这两个微服务都成功了,也就证明了我们的网关也搭建成功了。
1.5 微服务结构分析
通过以上步骤,我们创建了一个简单的 Gateway 网关,并配置了一个基本的路由规则,将请求转发到用户服务。整个 cloud-demo
微服务的结构如下图所示:
这个微服务结构图很好地描述了整个系统的架构和流程。以下是对图中各个部分的详细解释:
-
Gateway 网关:
- 网关是整个系统的入口点,它接收外部请求并将其路由到不同的微服务。
- 网关的端口为
10010
,并通过路由规则确定请求的目标微服务。
-
用户服务 (
userservice
) 微服务:- 用户服务处理与用户相关的请求,例如用户信息的获取和操作。
- 用户服务通过 Nacos 注册到服务发现中心,以便网关可以发现和路由请求到该服务。
-
订单服务 (
orderservice
) 微服务:- 订单服务处理与订单相关的请求,例如订单的创建和查询。
- 与用户服务类似,订单服务也通过 Nacos 注册到服务发现中心。
-
Nacos 注册中心:
- Nacos 提供服务注册和发现功能,允许微服务注册并发现其他服务的实例。
- 网关和微服务都通过 Nacos 注册中心来获取服务实例的列表,以实现负载均衡和动态路由。
-
请求流程:
- 用户通过浏览器访问网关的路径,例如
http://127.0.0.1:10010/user/1
。 - 网关根据路由规则确定将请求路由到
userservice
微服务。 - 网关通过 Nacos 获取
userservice
的服务实例列表。 - 网关选择一个服务实例,并使用负载均衡策略将请求转发给该实例。
userservice
微服务处理请求并返回结果给网关,然后网关将结果返回给用户。
- 用户通过浏览器访问网关的路径,例如
这个微服务结构图清晰地展示了整个系统的工作流程和各个组件之间的关系,有助于理解微服务架构的运作方式。通过网关和服务注册中心的协作,系统能够实现高可用性、负载均衡和动态扩展,以满足不同场景下的需求。
二、Gateway 断言工厂
在上文搭建 Gateway 网关服务的演示中,我们提到了路由断言(predicates)规则,而这些规则实际上是由断言工厂(Predicate Factory)处理的。断言工厂是 Spring Cloud Gateway 提供的一种机制,它们负责将配置文件中的字符串规则解析为具体的路由条件。
2.1 Spring 提供的断言工厂
Spring 提供了多种基本的断言工厂,每个工厂对应一种路由判断的条件。以下是一些常用的断言工厂及其示例:
名称 | 说明 | 示例 |
---|---|---|
After | 请求是否在某个时间点之后。 | After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 请求是否在某个时间点之前。 | Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 请求是否在某两个时间点之间。 | Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求是否包含指定的 Cookie。 | Cookie=chocolate, ch.p |
Header | 请求是否包含指定的 Header。 | Header=X-Request-Id, \d+ |
Host | 请求是否访问指定的 Host(域名)。 | Host=**.somehost.org, **.anotherhost.org |
Method | 请求的方法是否为指定的方式。 | Method=GET, POST |
Path | 请求的路径是否符合指定规则。 | Path=/red/{segment}, /blue/** |
Query | 请求参数是否包含指定参数。 | Query=name, Jack |
RemoteAddr | 请求者的 IP 是否在指定范围内。 | RemoteAddr=192.168.1.1/24 |
Weight | 权重处理,通常在负载均衡场景下使用。 | Weight=group1, group2 |
这些断言工厂为网关提供了灵活的路由规则配置,使得我们可以根据不同的条件来定制路由策略。在实际使用中,可以根据需求组合使用这些断言工厂,构建复杂而强大的路由规则。
2.2 示例:设置断言工厂
如果我们想要配置某个断言工厂,可以直接参考 Spring 的官方文档:
例如,我们要想设置 After 断言:
这个配置中,对于 orderservice
使用了两个断言:
-
Path 断言工厂:
Path=/order/**
表示请求的路径必须以/order/
开头才会匹配这个路由规则。
-
After 断言工厂:
After=2024-01-01T17:42:47.789+08:00[Asia/Shanghai]
表示请求的时间必须在指定的时间点之后,即在 2024 年 1 月 1 日 17:42:47(上海时区)之后。
通过这两个断言,路由规则要求请求路径必须以 /order/
开头,并且请求的时间必须在 2024 年 1 月 1 日 17:42:47 之后。只有同时满足这两个条件的请求才会匹配到这个路由规则,然后被路由到相应的微服务。
显然当前时间不符合 After 断言工厂,如果我们此时尝试访问,则会出现 404 错误:
那么如果此时将 After 改成 Before 断言工厂呢:
显然当前的时间符合要求,再次重启并访问,便可以成功访问了:
通过上述设置断言工厂的例子,我们就会发现其实设置某个断言工厂非常简单,并且也不需要我们刻意去记某个断言工厂,只需要在使用的时候查阅官方文档即可。
三、Gateway 路由过滤器及其工厂
3.1 路由过滤器 GatewayFilter
GatewayFilter 是网关中提供的一种强大的过滤器,允许对进入网关的请求和微服务返回的响应进行全面的处理。通过网关过滤器,我们可以在请求和响应的不同阶段执行自定义的逻辑,例如添加请求头、修改请求体、记录日志、验证权限等。
流程图解释:
- 用户的请求首先经过网关的路由进行转发,然后通过一层一层的过滤器链进行过滤。
- 过滤器的作用可以包括查看请求头、修改请求体、记录日志等操作,每个过滤器在过滤链中有特定的执行顺序。
- 经过一系列的过滤器处理后,请求最终发送给目标微服务。
- 微服务处理完请求后,响应的结果同样会经过网关的过滤器链进行处理,最终通过路由返回给用户。
通过合理配置和组合不同的网关过滤器,我们能够实现灵活、高效的请求处理流程。在实际应用中,可以根据具体的业务需求选择合适的过滤器,并有选择性地添加、移除或自定义过滤器,以实现定制化的网关处理逻辑。
3.2 路由过滤器工厂 GatewayFilter Factory
Spring 提供了三十多种不同的路由过滤器工厂,通过 Spring 官网文档 可以查询这些过滤器工厂具体的使用方法:
这些过滤器工厂包括但不限于:
-
AddRequestHeader GatewayFilterFactory: 为请求添加头信息。
yamlfilters: - AddRequestHeader=HeaderName, HeaderValue
-
AddResponseHeader GatewayFilterFactory: 为响应添加头信息。
yamlfilters: - AddResponseHeader=HeaderName, HeaderValue
-
RewritePath GatewayFilterFactory: 重写请求路径。
yamlfilters: - RewritePath=/foo/(?<segment>.*), /$\{segment}
-
SetPath GatewayFilterFactory: 设置请求路径。
yamlfilters: - SetPath=/foo
-
RequestRateLimiter GatewayFilterFactory: 请求速率限制。
yamlfilters: - RequestRateLimiter=RateLimitKey, replenishRate, burstCapacity
以上是一些常用的过滤器工厂,通过合理配置这些过滤器工厂,我们可以实现对请求和响应的灵活处理。在实际应用中,可以根据具体的需求选择相应的过滤器工厂,并按需组合使用,以实现定制化的网关处理逻辑。
3.3 示例:添加过滤器工厂
在这个示例中,我们可以给所有进入 userservice
服务的请求添加一个请求头:Hello=Hello GatewayFilterFactory!
,注意实际上这是一个键值对。
具体的操作则是在gateway
服务中修改application.yml
文件,给userservice
的路由添加过滤器:
验证是否添加请求头成功的方式则可以在 userservice
的请求用户信息方法中新增一个参数,例如:
java
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Hello", required = false) String hello) {
System.out.println("Hello: " + hello);
return userService.queryById(id);
}
在这个 Controller 方法中新增了一个 hello
参数,其来源是 HTTP 的请求头,使用 @RequestHeader
注解标注,并且使用 required
指定了这个参数不是必传的,然后重启 userservice
并在浏览器中进行访问,在控制台上成功打印了"Hello: Hello GatewayFilterFactory!" 日志:
3.4 默认过滤器
通过上面的示例,我们知道了如何给某个微服务的请求加上过滤器,但是如果要给所有服务的请求都加上过滤器该如何操作呢?
可能我们首先会想到分别给各个微服务都加上过滤器的配置项,但是这样就显得非常冗余了。此外,我们还可以使用默认过滤器(default-filters
)的方式来给所有的微服务请求都加上相同的过滤器配置,例如:
此时,我们可以尝试把 userservice
中的过滤器配置给注释掉,然后再次重启服务并访问,看看默认过滤器配置是否生效。
此时便说明我们的默认过滤器配置生效了。
四、Gateway 全局过滤器
在前文中,我们已经了解了请求路由的过滤器工厂以及默认过滤器 default-filters
的作用。现在,让我们深入研究另一个强大的概念------全局过滤器 GlobalFilter
。
4.1 全局过滤器的概念和作用
1. 概念
全局过滤器是 Spring Cloud Gateway 中的一种过滤器类型,它的作用是处理所有进入网关的请求和从微服务返回的响应。与局部过滤器(GatewayFilter)不同,全局过滤器的逻辑需要通过代码
来实现,而不是通过配置
来定义。
全局过滤器是在整个请求-响应周期中起作用的过滤器。它可以执行一些全局性的任务,如认证、授权、日志记录、性能监控等。全局过滤器不仅可以处理请求,还可以处理从微服务返回的响应,使其成为一个更为强大和灵活的过滤器类型。
2. 作用
-
统一处理全局任务:
全局过滤器可以用于执行与整个微服务架构相关的任务,而不仅仅是单个路由或微服务的请求处理。这使得它非常适合于执行全局性的操作,例如在每个请求中执行身份验证、记录请求日志等。
-
修改请求和响应:
通过全局过滤器,可以在请求到达微服务之前或响应返回客户端之前修改请求或响应。这种能力对于实施诸如请求重写、响应重写、添加头信息等操作非常有用。
-
集成外部服务
全局过滤器还可以用于集成外部服务,例如在请求中调用身份验证服务或其他微服务,以获取必要的信息或执行某些任务。
4.2 GlobalFilter 接口定义
java
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过 {@link GatewayFilterChain} 将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取 Request、Response 等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标识当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
上述代码展示了 GlobalFilter 接口的定义。其中关键部分的说明如下:
-
filter
: 这是 GlobalFilter 接口的核心方法,负责处理当前请求。参数ServerWebExchange
提供了请求上下文,其中包含了请求和响应的相关信息。参数GatewayFilterChain
则用于将请求委托给下一个过滤器处理。 -
返回类型
Mono<Void>
表示当前过滤器的业务逻辑是否已经处理完毕。如果返回的Mono
完成了,那么表示当前过滤器的任务已完成,请求将继续传递给下一个过滤器。如果Mono
未完成,请求将被阻塞。
4.3 示例:定义全局过滤器进行用户身份验证
在微服务架构中,用户身份验证是保护服务安全的重要一环。通过定义全局过滤器,我们可以在请求进入网关时对用户身份进行验证,以确保只有合法用户才能访问服务。本示例将展示如何使用 Spring Cloud Gateway 实现一个简单的用户身份验证全局过滤器。
1. 需求说明
我们的需求是拦截请求,判断请求参数中是否包含 authorization
参数,且其值是否为 "admin"。如果满足条件,请求将被放行;否则,请求将被拦截。
2. 实现过程
首先,我们创建一个全局过滤器 AuthorizeFilter
,并实现 GlobalFilter
接口:
java
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2. 获取参数中的 authorization 参数
String authorization = params.getFirst("authorization");
// 3. 判断参数值是否等于 admin
if("admin".equals(authorization)){
// 4. 等于则放行,相当于调用过滤器链中的下一个过滤器
return chain.filter(exchange);
}
// 5. 不等于则拦截
// 5.1 设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2 拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
在这个过滤器中,我们获取请求参数中的 authorization
参数,并判断其值是否为 "admin"。如果是,请求将继续传递给过滤器链中的下一个过滤器;否则,设置响应状态码为 UNAUTHORIZED,表示请求未被授权,并终止请求处理。
3. 过滤器执行顺序
过滤器的执行顺序可以通过 @Order 注解的方式实现,另外也可以通过实现 Ordered 接口并重写其中的 getOrder
方法。在上述代码中,通过实现 Ordered
接口,我们可以指定过滤器的执行顺序。在这个例子中,通过 getOrder
方法返回 -1
,表示这个过滤器的执行顺序较高,会在一般过滤器之前执行。
4. 验证全局过滤器是否生效
重启 gateway
服务,首先直接访问userservice
不带任何参数:
发现访问失败,并且响应码为 401
。
加上 authorization
参数再进行访问:
此时便可以成功访问了,当然,如果 authorization
参数的值不为 admin
也会访问失败:
通过定义这个简单的全局过滤器,我们实现了对用户身份的基本验证。在实际应用中,用户身份验证通常会更为复杂,可能涉及调用认证服务、检查 token 签名等操作。这个示例提供了一个基本的框架,可以根据实际需求进行扩展,确保微服务的安全性和可靠性。
五、过滤器链的执行顺序
在 Spring Cloud Gateway 中,过滤器链的执行顺序是确保请求经过一系列过滤器并按照特定的规则执行的关键。一般而言,请求进入网关后会遇到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。
5.1 过滤器链执行过程
请求路由后,当前路由过滤器、DefaultFilter、GlobalFilter 会被合并到一个过滤器链(集合)中,并按照排序规则进行排序,然后按照排序后的顺序依次执行。这个过程如下图所示:
5.2 过滤器的排序规则
为了确保过滤器按照正确的顺序执行,遵循以下排序规则:
-
自定义过滤器: 每个自定义过滤器必须指定一个整数类型的
order
值,数值越小,优先级越高,执行顺序越靠前。 -
GlobalFilter: 全局过滤器通过实现
Ordered
接口或添加@Order
注解来指定order
值,由开发者自行指定。值越小,优先级越高。 -
路由过滤器和 DefaultFilter: 路由过滤器和 DefaultFilter 的
order
由 Spring 框架指定,默认按照声明顺序从1递增。 -
相同
order
值处理: 当过滤器的order
值相同时,执行顺序为 DefaultFilter > 路由过滤器 > GlobalFilter。具体实现可以查看一些关键类的源码,如org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
和org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
。
过滤器链的执行顺序非常关键,它保证了过滤器能够按照开发者期望的顺序执行。通过合并路由过滤器、DefaultFilter 和 GlobalFilter,并按照指定的执行顺序排序,网关可以对请求进行精细控制,实现各种定制化的业务逻辑。在实际开发中,了解和正确使用过滤器链的执行规则是确保网关正常工作的基础。
5.3 为什么三种不同的过滤器可以进行排序形成过滤器链
首先对于路由过滤器和默认过滤器中的AddRequestHeader
过滤器工厂来说,它们的底层都是由AddRequestHeaderGatewayFilterFactory
类实现的。
通过查看 AddRequestHeaderGatewayFilterFactory
类的源码可以知道,它们最后最后转化成一个相同类型的对象,即GatewayFilter
:
但是对于 GlobalFilter
和 GatewayFilter
这两个接口来说,查看它们的源码并没有发现继承关系:
那么是否就意味着它们之间没有关系了呢?其实不然,在 FilteringWebHandler
这个类中有一个静态内部类GatewayFilterAdapter
:
可以发现这个类实现了GatewayFilter
接口,并且其中有一个属性,其类型是GlobalFilter
。可以发现可以类的作用是把GlobalFilter
适配成了GatewayFilter
。
因此,这三种过滤器最终都可以当成GatewayFilter
,所有它们之间可以按照Ordered
接口、@Order
注解、默认等指定的顺序放在同一个过滤器链中。
六、跨域问题
6.1 什么是跨越问题
跨域问题是指在 web 应用程序中,由于安全策略的限制,浏览器禁止在一个源(origin)的网页应用程序中发起对另一个源的 HTTP 请求。这个源包括协议、域名、端口的组合,只有当两个请求的源相同时,浏览器才允许跨域请求。
跨域问题主要由以下情况引起:
-
不同域名: 当两个请求的域名不同,即使协议和端口相同,也会被认为跨域。例如,
www.example.com
和api.example.com
。 -
不同端口: 即使在同一个域名下,如果请求的端口不同,也会被认为跨域。例如,
example.com:8080
和example.com:3000
。 -
不同协议: 当一个请求使用 HTTP 协议,而另一个使用 HTTPS 协议时,也会被视为跨域。
跨域问题的存在是为了增加 web 安全性,防止恶意网站利用用户的浏览器向其他网站发起恶意请求,窃取用户信息等。
解决跨域问题的常见方式之一是使用 CORS(跨域资源共享)策略,它允许服务器在响应中附加一些标头,告诉浏览器哪些域名允许跨域访问资源。当浏览器发起跨域请求时,会先发送一个预检请求(OPTIONS 请求)给服务器,服务器根据预检请求的信息来判断是否允许跨域访问。如果服务器返回合适的响应头,浏览器才会允许跨域请求。这种方式可以有效地解决跨域问题,同时保障了网站的安全性。
6.2 演示跨越问题
为了演示跨域问题,我们首先创建一个简单的 HTML 文件 index.html
,其中包含了一个使用 Axios
发起的 AJAX 请求:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<pre>
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:5500"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
</script>
</html>
说明:
此时,我们假设 http://localhost:10010/user/1
是一个需要进行跨域访问的服务。然后,使用 VS Code 中的 Live Server 插件启动 index.html
,然后通过浏览器的开发者工具查看控制台的输出内容:
发现出现了跨越问题,导致不能正常访问userservice
的服务。
6.3 使用 Gateway 解决跨域问题
6.3 使用 Gateway 解决跨域问题
为了解决跨域问题,我们可以在 gateway
服务的 application.yml
中新增如下的配置,使用 Spring Cloud Gateway 的跨域配置:
yaml
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:5500"
allowedMethods: # 允许的跨域 ajax 的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带 cookie
maxAge: 360000 # 这次跨域检测的有效期
说明:
allowedOrigins
:允许跨域请求的源,这里设置为http://localhost:5500
。allowedMethods
:允许的跨域 AJAX 请求方式,包括GET
、POST
、DELETE
、PUT
、OPTIONS
。allowedHeaders
:允许在请求中携带的头信息,设置为"*"
表示允许所有头信息。allowCredentials
:是否允许携带 Cookie。maxAge
:这次跨域检测的有效期,设置为360000
毫秒。
通过这样的配置,我们告诉 Spring Cloud Gateway 允许指定的源(http://localhost:5500
)发起跨域请求,并指定了其他的一些跨域配置。
然后,重新访问 localhost:5500
,发现控制台成功输出了访问userservice
的结果了: