Spring Cloud Gateway
是 Spring Cloud 生态中作为网关的组件,它基于 WebFlux 框架实现,使用的是 Reactor 的编程模式,底层则使用了高性能的通信框架 Netty。本文主要讲述 Spring Cloud Gateway
的使用,如路由、统一认证、限流等功能。
概述
基本概念
Route(路由):
网关配置的基本组成模块,一个 Route 模块由一个 ID,一个目标 URI,一组断言和一组过滤器组成。如果断言为真,则路由匹配,目标 URI 会被访问。
Predicate(断言):
这是一个 Java 8 的 Predicate
,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange
。
Filter(过滤器)
它们 Spring Framework GatewayFilter
的实例类,可以在向下游发送请求之前或之后修改请求和响应数据。
工作流程
下图展示了 Spring Cloud Gateway
的工作流程:
- 客户端向
Spring Cloud Gateway
发送请求; - 如果
Gateway Handler Mapping
判定请求匹配到一个Route
, 则把请求发送到Gateway Web Handler
; Gateway Web Handler
执行一个过滤器链来处理请求,过滤器可以在发送请求之前执行,也可以在之后执行;- 所有的
pre
过滤器在发送请求前执行,所有的post
过滤器将在发送请求收到响应之后执行; - 过滤器可以修改请求和响应的数据。
前置条件
提前约定,本文涉及到版本如下:
1 | <!-- 依赖包版本管理 --> |
路由
路由分为两类:1)基本路由,指定路由的目的地址; 2) 服务路由,从服务注册中心选取路由的地址。其配置如下所示:
1 | spring: |
一个 Route 包含如下的内容:
- id: 唯一标识这个 Route;
- uri: 路由的目的地址,可以指定一个地址,如
http://ip:port
,也可以指定服务名称,如lb://serviceName
; - predicates: 断言,路由的判定条件;
- filters: 过滤器,可以修改请求或响应的内容。
Predicate
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由,如下图所示:
Filter
Filter 可以对请求或响应进行修改,常用的过滤器有:
过滤规则 | 实例 | 说明 |
---|---|---|
StripPrefix | - StripPrefix=1 | 从请求路径中移除一个前缀,如 /configs/cache –> /cache |
PrefixPath | - PrefixPath=/app | 在请求路径前加上 app |
RewritePath | - RewritePath=/test,/app/test | 访问 localhost:9022/test, 请求会转发到 localhost:8001/app/test |
SetPath | SetPath=/app/{path} | 通过模板设置路径,转发的规则时会在路径前增加 app,{path} 表示原请求路径 |
RemoveRequestHeader | - RemoveResponseHeader=X-Response-Foo | 返回给客户端前去掉某个请求头信息 |
AddRequestHeader | - AddRequestHeader=X-Request-red, blue | 向下游请求前加上某个请求头信息 |
更多 Filter 可以参考官网:Filter 配置
集成服务注册中心
在 Spring Cloud Gateway
中可以集成服务注册中心,网关也可以作为服务消费方注册上去,在这里,以 Nacos
为例。
配置
1 | spring: |
开启服务发现
在启动类加上 @EnableDiscoveryClient
annotation.
Nacos
的安装使用可以参考本系列的相关文章。
Maven 依赖
需要引入 Nacos
及 Spring Cloud Gateway
相关的包:
1 | <dependency> |
统一认证
假定已经有认证服务器且使用 JWT Token
, Spring Cloud Gateway
在认证体系中主要是作为资源服务器的角色,从请求中获取 JWT Token
, 并验证其合法性。整体上来说,分为两个步骤:
- 实例化
SecurityWebFilterChain
对象,设置安全相关的参数; - 开启资源服务器功能。
开启资源服务器功能
第二步相对比较简单,在资源服务器的配置类上加入 @EnableWebFluxSecurity
annotation 即可,如下图所示:
1 |
|
实例化 SecurityWebFilterChain
对象
SecurityWebFilterChain
对象是 SpringSecurity
体系中比较重要的类,它代表了一个请求处理链,定义的形式如下:
1 |
|
自定义 JWT 权限转换器
由于 Gateway 没有将 JWT Claim 中 authorities 值加入到 Authentication 权限里面,所以需要自定义一个 JwtGrantedAuthoritiesConverter
对象,提取 JWT Claim
中的 authorities 权限值。
1 |
|
另外,Gateway 作为资源服务器,需要访问认证服务器获取解密的密钥,所以在配置文件中配置认证服务器获取密钥的 URL.
1 | spring: |
自定义 token 无效或者已过期处理逻辑
当 token 无效或已过期,自定义一个返回结果,并指定一个错误码。
1 |
|
自定义 “访问禁止” 的异常处理逻辑
与 token 无效或已过期一样,统一定义一个错误码返回即可。
1 |
|
自定义 Filter
可以根据需求,在 FilterChain 中加入自定义的 Filter, 处理特定的业务,在这里,我们加入一个 Filter, 用于清除请求头 Authorization
中的内容。
1 |
|
自定义 AuthorizationManager
除了白名单中的 URL,其它 URL 都需要进行权限判断。在这里,需要自定义一个 ReactiveAuthorizationManager
管理器,封装访问权限逻辑。其形式如下所示:
1 |
|
访问权限的判定可以用一句话来描述:获取有权限访问该请求 URL 的角色列表,然后判断 JWT 中携带的用户角色是否包含在角色列表中,如果包含,则通过,反之则不通过。
Maven 依赖
需要引入 SpringSecurity
, Oauth2
相关的包:
1 | <dependency> |
限流
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
- route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId;
- 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组。
引入 Sentinel
引入 Sentinel
包含如下步骤:
- 加入 Sentinel 配置文件;
- 加入自定义业务逻辑。
加入 Sentinel 配置文件
1 | spring: |
加入自定义业务逻辑
整体的结构如下所示:
1 |
|
自定义业务逻辑包括:
- 自定义限流处理器:可以自定义返回的业务信息,否则返回默认的信息,如:Blocked by Sentinel: FlowException;
- 自定义 api 组:可以将分散的 URL 加入到一个分组,使用统一的限流逻辑;
- 自定义网关 Rules:可以根据 Roule ID, api 分组进行限流配置;
自定义限流处理器
可以将限流的异常转译为业务上错误码返回。
1 | public void initBlockHandlers() { |
自定义 api 组
将 /configs/**
,/services/**
归到一个 api 分组,并命名为 some_customized_api
, 用于后续的限流配置。1
2
3
4
5
6
7
8
9
10
11
12private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/configs/**"));
add(new ApiPathPredicateItem().setPattern("/services/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
自定义网关 Rules
为 koala-oauth
,koala-oauth2-api
Route ID 设置不同的限流参数,同时也可以为 some_customized_api
配置限流参数。
1 | private void initGatewayRules() { |
启动控制台
1 | java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.app.type=1 -jar sentinel-dashboard.jar |
其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080, -Dcsp.sentinel.app.type 用于指定网关类型。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
Maven 依赖
1 | <!-- Sentinel流量控制、熔断降级 --> |
工程代码:https://github.com/noahsarkzhang-ts/springboot-lab/tree/main/springcloud-koala
参考: