Gateway 进阶使用
# Gateway 进阶
在 SpringCloud Gateway 中,断言工厂 和 过滤器工厂 是实现网关功能的核心组件,它们帮助我们对请求进行条件匹配和处理,最终实现灵活的路由配置和请求管理。
# 1. 断言工厂 (Predicate Factory)
断言工厂用于根据请求的属性判断该请求是否符合路由规则。例如,路径匹配规则 Path=/user/**
由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
处理。类似的断言工厂在 SpringCloud Gateway 中有十几种。
常见的断言工厂包括:
名称 | 说明 | 示例 |
---|---|---|
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,2037-01-21T17:42:47.789-07:00 |
Cookie | 请求必须包含指定的 Cookie | Cookie=chocolate,ch.p |
Header | 请求必须包含指定的 Header | Header=X-Request-Id,\d+ |
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 | 权重处理 | 配置权重路由的负载分配 |
说明:我们只需要掌握Path这种路由工程就可以了。
Path=/user/**
是基于路径的断言,匹配所有以/user/
开头的请求。- 断言工厂在 Gateway 中起到条件判断的作用,可以通过配置实现灵活的路由规则。
# 2. 过滤器工厂 (Filter Factory)
过滤器工厂在网关中用于处理请求和响应,可以添加或移除请求头、修改请求路径、限流等操作。SpringCloud Gateway 提供了丰富的内置过滤器工厂,常见的有请求头过滤、限流过滤、路径修改过滤等。
内置的过滤器工厂可以根据用途分为以下几类:
- Header 相关:如
AddRequestHeader
、RemoveRequestHeader
等。 - Parameter 相关:如
AddRequestParameter
、RemoveRequestParameter
等。 - Path 相关:如
RewritePath
、StripPrefix
等。 - Body 相关:如
ModifyRequestBody
、ModifyResponseBody
等。 - Status 相关:如
SetStatus
、SetResponseStatus
等。 - Session 相关:如
SaveSession
、RedisRateLimiter
等。 - Redirect 相关:如
RedirectTo
等。 - Retry 相关:如
Retry
过滤器。 - RateLimiter:如
RequestRateLimiter
限流过滤器。 - Hystrix:如
Hystrix
过滤器,用于熔断处理。
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
# 1. 常见的过滤器工厂
SpringCloud Gateway 提供了 37 种不同的路由过滤器工厂。例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除某个响应头 |
RequestRateLimiter | 限制请求流量 |
这些过滤器工厂可以帮助我们灵活地管理和处理网关中的请求。
# 2. 请求头过滤器示例
下面以 AddRequestHeader
为例,演示如何为所有进入 userservice
的请求添加一个自定义请求头 Truth=给请求头加过滤器测试!
。
在 gateway
服务的 application.yml
文件中配置路由过滤器:
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: userservice # 路由id,自定义,只要唯一即可
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
filters: # 过滤器
- AddRequestHeader=Truth, 给请求头加过滤器测试! # 添加请求头
- id: orderservice # 路由id,自定义,只要唯一即可
uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/order/** # 这个是按照路径匹配,只要以/order/开头就符合要求
2
3
4
5
6
7
8
9
10
11
12
13
14
重点说明:
- 当前
AddRequestHeader
过滤器配置在userservice
路由下,因此仅对访问userservice
的请求生效,其他路由(如orderservice
)不会受此影响。
# 3. 全局默认过滤器
如果希望对所有路由生效,可以使用全局默认过滤器:
spring:
cloud:
gateway:
routes:
- id: userservice
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, 给请求头加过滤器测试! # 添加请求头
2
3
4
5
6
7
8
9
10
说明:
- 全局默认过滤器会对所有经过网关的请求生效,无论路由规则如何。
小结
- 断言工厂:用于判断请求是否符合路由规则。常见的断言包括
Path
、Method
、Header
等,可以根据请求的路径、方法、头信息等进行路由匹配。 - 过滤器工厂:用于对请求或响应进行处理,如添加请求头、限流、修改路径等。过滤器可以配置在特定路由下,也可以作为全局过滤器,提供灵活的请求管理能力。
- 实用场景:通过合理配置断言和过滤器,可以实现复杂的网关逻辑,包括限流、鉴权、负载均衡等功能,使得微服务架构中的流量管理更加高效和灵活。
# 3. Gateway 全局过滤器
在使用 SpringCloud Gateway 时,内置的过滤器虽然功能丰富,但逻辑是固定的。如果我们需要在请求进入网关时执行自定义逻辑,如登录校验、权限验证等操作,就需要使用全局过滤器。
# 1. 全局过滤器的作用
全局过滤器与 GatewayFilter 的作用相似,都是用于处理进入网关的请求和微服务的响应。区别在于:
- GatewayFilter 是通过配置实现,处理逻辑固定。
- GlobalFilter 是通过代码实现,可以自定义处理逻辑。
全局过滤器的定义需要实现 GlobalFilter
接口:
public interface GlobalFilter {
/**
* 处理当前请求,可以通过 {@link GatewayFilterChain} 将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,包含 Request、Response 等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 表示当前过滤器处理完毕
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
2
3
4
5
6
7
8
9
10
在 filter
方法中编写自定义逻辑,可以实现以下功能:
- 登录状态判断
- 权限校验
- 请求限流等
# 2. 自定义全局过滤器示例
需求:定义一个全局过滤器,拦截请求并判断请求的参数是否满足以下条件:
- 请求参数中是否包含
authorization
。 authorization
参数的值是否为admin
。
如果以上条件同时满足,则放行请求;否则拦截请求。
# 2.1 自定义全局过滤器
在 gateway
服务中创建自定义过滤器 AuthorizeFilter
:
package cn.itcast.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Mono;
@Order(-1) // 指定过滤器的顺序,order 值越小,优先级越高
@Component // 将过滤器注册为 Spring 容器中的组件
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2. 获取 authorization 参数
String auth = params.getFirst("authorization");
// 3. 校验 authorization 参数
if (auth == null || "".equals(auth)) {
// 如果没有提供 authorization 参数,返回 401 Unauthorized 状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 拦截请求
}
if ("admin".equals(auth)) {
// 如果 authorization 参数为 "admin",放行请求
return chain.filter(exchange);
}
// 如果 authorization 参数不为 "admin",返回 403 Forbidden 状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete(); // 拦截请求
}
}
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
重点说明:
ServerWebExchange
提供了请求和响应的上下文信息。- 通过
order
属性或实现Ordered
接口来控制过滤器的执行顺序。
# 2.2 测试过滤器效果
重启网关服务,访问 http://localhost:10010/user/1
,由于没有提供 authorization
参数,因此请求被拦截:
添加 authorization=admin
参数后,访问 http://localhost:10010/user/1?authorization=admin
,请求被放行并成功返回结果:
# 3. 过滤器执行顺序
请求进入网关时,会经过三类过滤器:
- 当前路由的 过滤器 (如配置在
application.yml
中的路由过滤器)。 - DefaultFilter:全局默认过滤器。
- GlobalFilter:自定义的全局过滤器。
过滤器执行顺序:
- 默认过滤器 (DefaultFilter)
- 路由过滤器 (配置在特定路由下的过滤器)
- 全局过滤器 (GlobalFilter)
过滤器的执行顺序由 order
值决定,order
值越小,优先级越高,执行顺序越靠前。
# 3.1 通过实现 Ordered
接口设置顺序
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 过滤逻辑同前
}
@Override
public int getOrder() {
return -1; // order 值越小,优先级越高
}
}
2
3
4
5
6
7
8
9
10
11
12
# 3.2 通过 @Order
注解设置顺序
@Component
@Order(-1) // 指定过滤器的顺序
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 过滤逻辑同前
}
}
2
3
4
5
6
7
8
注意:路由过滤器和 DefaultFilter 的
order
值由 Spring 自动分配,通常按照声明顺序从 1 开始递增。当多个过滤器的order
值相同时,执行顺序为:
- DefaultFilter
- 路由过滤器
- GlobalFilter
详细内容,可以查看执行顺序的源码:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
# 4. 全局过滤器小结
- 全局过滤器:实现自定义逻辑的过滤器,用于处理所有进入网关的请求和响应。
- 过滤器执行顺序:由
order
值控制,值越小,优先级越高。 - 测试方法:通过指定请求参数控制过滤器的放行和拦截逻辑,实现自定义的业务规则。
# 4. Gateway 跨域问题详解
# 1. 什么是跨域请求
跨域请求是指当前发起请求的域与请求目标资源所在的域不同时的请求。跨域的判断标准包括协议、域名、端口号三者中的任何一个不同。
常见的跨域问题场景如下:
- 前端页面从
http://localhost:8080
请求后端 APIhttp://localhost:8081
。 - 不同域名、子域名或端口之间的资源请求。
当跨域请求发生时,如果服务器未正确处理跨域问题,浏览器会基于同源策略(Same-Origin Policy)拒绝请求。
# 2. 跨域请求的完整流程
跨域请求通常涉及两个请求过程:
- 预检请求(OPTIONS 请求):在发送实际请求前,浏览器会首先发起一个 OPTIONS 请求,用于验证服务器是否允许跨域请求。如果服务器允许跨域请求,则浏览器会继续发送实际请求。
- 实际请求:在预检请求成功后,浏览器会发送实际的跨域请求,服务器响应请求并返回数据。
注意:如果服务器配置了允许所有源的请求(使用通配符
*
),或者源是相同的,同源策略不适用,那么可能不会触发预检请求。但这通常不是最佳安全实践。
# 3. 同源策略
同源策略(Same-Origin Policy)是一种浏览器安全机制,它限制了脚本在不同源之间自由交互数据。其主要目的是防止恶意网站获取用户敏感信息或执行恶意操作。
同源指的是协议、域名和端口号完全相同的两个 URL。如果这三者中任何一个不同,就会触发跨域问题。
# 4. SpringBoot 中的跨域解决方案
# 4.1 使用 Spring 提供的跨域配置
Spring 提供了简化跨域配置的机制,可以通过注解或全局配置来解决跨域问题。
使用
@CrossOrigin
注解:可以在 Controller 层为特定方法或整个类配置跨域规则。全局跨域配置:使用
WebMvcConfigurer
配置全局跨域规则:@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 设置路径匹配模式 .allowedOrigins("http://localhost:9000") // 允许的跨域来源 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的 HTTP 方法 .allowedHeaders("Origin", "Content-Type", "Accept") // 允许的 HTTP 头部 .allowCredentials(true) // 是否允许携带凭证 .maxAge(3600); // 缓存时间 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
说明:上述配置允许
http://localhost:9000
进行跨域请求,并且支持特定的 HTTP 方法和头部。
# 4.2 在微服务架构中通过 Gateway 配置跨域
在 SpringCloud Gateway 项目中,可以在 application.yml
中配置全局跨域处理:
spring:
cloud:
gateway:
globalcors: # 全局跨域配置
add-to-simple-url-handler-mapping: true # 处理 OPTIONS 请求
corsConfigurations:
'[/**]': # 对所有路径生效
allowedOrigins: # 允许的跨域来源
- "http://localhost:9000"
allowedMethods: # 允许的请求方法
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许的请求头
allowCredentials: true # 是否允许携带凭证
maxAge: 360000 # 预检请求的有效期
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
重点说明:
globalcors
配置项用于全局跨域处理,add-to-simple-url-handler-mapping
解决 OPTIONS 请求被拦截的问题。allowedOrigins
和allowedMethods
分别配置允许的跨域来源和请求方法。
# 4.3 通过 Nginx 反向代理解决跨域问题
在使用 Nginx 时,可以在配置文件中添加跨域处理:
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 添加跨域头部信息
add_header 'Access-Control-Allow-Origin' 'http://localhost:8080';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# 处理 OPTIONS 请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
重点说明:
proxy_pass
配置了反向代理的目标地址。add_header
添加了跨域头部,允许指定来源的请求。
# 5. 跨域问题总结
- 跨域问题的必要性和重要性:跨域问题的处理是确保安全性、用户隐私和系统稳定性的关键。
- 选择合适的跨域解决方案:根据项目需求和部署环境,选择合适的跨域解决方案,例如注解配置、全局配置、网关配置或 Nginx 配置。
- 分布式系统中的跨域问题:在微服务架构中,跨域问题尤为常见,特别是在多个服务之间进行通信时,正确的跨域配置至关重要。
通过对跨域的理解和解决方案的掌握,可以确保系统在复杂环境中依然保持稳定、安全和高效的运行。