SpringCloud网关Gateway

1.网关路由

1.1 认识网关

网关就是络的口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验

  • 在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平 台无关的服务协议作为各个单元间的通讯方式。
  • 网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

1.2 网关组成

网关 = 路由转发 + 过滤器(编写额外功能)

1. 路由转发

接收外界请求,通过网关的路由转发,转发到后端的服务上。

如果只有这个一个功能看起来和之前学习的Nginx反向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应服务器上。

2.过滤器

网关非常重要的功能就是过滤器。

网关过滤器链中的过滤器有两种:

  • GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

1.3 如何工作

image-20231226182212330

客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的 "pre" (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post" (后)过滤器逻辑被运行。

2.快速入门

  • 创建网关微服务
  • 引入SpringCloudGateway、NacosDiscovery依赖
  • 编写启动类
  • 配置网关路由

2.1 创建项目

创建一个新的module作为网关微服务

2.2 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--网关-->
<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>

2.3 添加启动类

2.4 配置路由

application.yaml文件中添加路由配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.163.129:8848
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
- Path=/items/**,/search/** # 这里是以请求路径作为判断规则
### 请求路径前加上/app
filters:
- PrefixPath=/app

gateWay的主要功能之一是转发请求,转发规则的定义主要包含三个部分

Route(路由) 路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。
Predicate(谓语、断言) 路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等,写法必须遵循 key=vlue的形式
Filter(过滤器) 过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容

说明:路由匹配的规则有很多,上面是基于路径path匹配。

其余规则请查看官网:

image-20231226183526481

3.网关过滤器

Spring Cloud Gateway 根据作用范围划分为 GatewayFilter 和 GlobalFilter,二者区别如下:

GatewayFilter:网关过滤器,需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过 spring.cloud.default-filters 配置在全局,作用在所有路由上。
GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

3.1 GatewayFilter:网关过滤器

网关过滤器用于拦截并链式处理 Web 请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。修改传入的 HTTP 请求或传出 HTTP 响应。Spring Cloud Gateway 包含许多内置的网关过滤器工厂一共有 22 个,包括头部过滤器、 路径类过滤器、Hystrix 过滤器和重写请求 URL 的过滤器, 还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。

img

比如:Header头部过滤器AddRequestHeader GatewayFilter工厂需要一个 namevalue 参数。下面的例子配置了一个 AddRequestHeader GatewayFilter

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue

这个配置将 X-Request-red:blue header添加到所有匹配请求的下游请求的header信息中。

其他的GatewayFilter 工厂可以查询官网Spring Cloud Gateway 中文文档 (springdoc.cn)

3.2 GlobalFilter:全局过滤器

全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它是请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

Spring Cloud Gateway 自带许多实用的Global Filter:Spring Cloud Gateway 中文文档 (springdoc.cn)

image-20231226202011363

3.2.1 自定义全局过滤器

尽管Spring Cloud Gateway 自带许多实用的 Global Filter,但是在很多情景下我们仍然希望可以自定义自己的过滤器,用来满足自己的需求。

自定义网关过滤器需要实现以下两个接口 :GatewayFilterOrdered

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 编写过滤器逻辑
System.out.println("未登录,无法访问");
// 放行
// return chain.filter(exchange);

// 拦截
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}

@Override
public int getOrder() {
// 过滤器执行顺序,值越小,优先级越高
return 0;
}
}

通过自定义网关全局过滤器可以实现登录校验。基于token的校验示例:

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
package com.hmall.gateway.Filter;

import cn.hutool.core.text.AntPathMatcher;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
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.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
* @Author wzy
* @Date 2023/12/18 20:58
* @description: 全局登录过滤器
*/
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

private final AuthProperties authProperties;

private final JwtTool jwtTool;
//路径匹配工具
private final AntPathMatcher antPathMatcher = new AntPathMatcher();

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获得request对象
ServerHttpRequest request = exchange.getRequest();
//判断是否是需要做登录拦截
if(isExclude(request.getPath().toString())){
//放行
return chain.filter(exchange);
}
//获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if(headers!=null && !headers.isEmpty()){
token = headers.get(0);
}

Long userId = null;
//检验并解析token
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) {
//401,拦截 设置响应状态码为401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// TODO: 2023/12/18 传递用户信息
String userInfo = userId.toString();
ServerWebExchange webExchange = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo)).build();//保存用户信息到请求头中
System.out.println("userId"+userId);
return chain.filter(webExchange);
}

private boolean isExclude(String path) {
for (String excludePath : authProperties.getExcludePaths()) {
if(antPathMatcher.match(excludePath,path)){
return true;
}
}
return false;
}

@Override
public int getOrder() {
return 0;
}
}