目录
1.网关概述
2.网关路由
3.网关登录校验
3.1自定义过滤器
3.2实现登录校验
3.3微服务获取用户
3.4OpenFeign在不同微服务之间传递用户
4.网关配置管理
5.配置热更新
6.动态路由
1.网关概述
顾明思议,网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。更通俗的来讲,网关就像是以前园区传达室的大爷。现在,微服务网关就起到同样的作用。网关的核心功能有:
- 请求路由 (Routing):网关的核心职责是将客户端请求转发到后端的服务
- 登录校验:集成 OAuth、JWT、API Key 等认证机制,在请求转发前验证用户身份和权限
- 负载均衡:将流量均匀分配到后端服务节点,提高系统可靠性和性能
2.网关路由
前端请求不能直接访问微服务,而是要请求网关:
- 网关可以做安全控制,也就是登录身份校验,校验通过才放行
- 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去
Route路由:判断选择服务的目的地址,网关的基本构成由一个ID、一个目的地URI、一个断言(Predicate)集合和一个过滤器(Filter)集合定义。如果断言为真,则路由被匹配。
在SpringCloud当中,提供了两种网关实现方案:
- Netflix Zuul:早期实现,目前已经淘汰
- SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强
由于网关本身也是一个独立的微服务,因此也需要创建一个模块开发功能,快速入门:day04-微服务02 - 飞书云文档
路由语法规则需要掌握:Path写的是controller中的接口路径
微服务02-02.网关路由-快速入门_哔哩哔哩_bilibili
spring:
cloud:
gateway:
routes:
- id: item-service
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
四个属性含义如下:
id
:路由的唯一标示predicates
:路由断言,其实就是匹配条件filters
:路由过滤条件,后面讲uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问
这里我们重点关注predicates
,也就是路由断言。SpringCloudGateway中支持的断言类型有很多:
3.网关登录校验
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。但是如果每个微服务都需要单独校验JWT又太过麻烦, 所以既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了:
- 只需要在网关和用户服务保存秘钥
- 只需要在网关开发登录校验功能
如图中所示,最终请求转发是有一个名为NettyRoutingFilter
的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter
之前,这就符合我们的需求了。
3.1自定义过滤器
那么,该如何实现一个网关过滤器呢?网关过滤器链中的过滤器有两种:
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
GlobalFilter
:全局过滤器,作用范围是所有路由,不可配置
首先是GlobalFilter:定义过滤器最重要的就是filter方法微服务02-05.网关登录校验-自定义GlobalFilter_哔哩哔哩_bilibili
接着是自定义GatewayFilter,比较麻烦,真实开发中用得比较少,可以暂时不学习
微服务02-06.网关登录校验-自定义GatewayFilter_哔哩哔哩_bilibili
3.2实现登录校验
登录校验需要Jwt工具和编写过滤器,核心逻辑如下:
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final JwtTool jwtTool;
private final AuthProperties authProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取Request
ServerHttpRequest request = exchange.getRequest();
// 2.判断是否不需要拦截
if(isExclude(request.getPath().toString())){
// 无需拦截,直接放行
return chain.filter(exchange);
}
// 3.获取请求头中的token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if (!CollUtils.isEmpty(headers)) {
token = headers.get(0);
}
// 4.校验并解析token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) {
// 如果无效,拦截
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}
// TODO 5.如果有效,传递用户信息
System.out.println("userId = " + userId);
// 6.放行
return chain.filter(exchange);
}
private boolean isExclude(String antPath) {
for (String pathPattern : authProperties.getExcludePaths()) {
if(antPathMatcher.match(pathPattern, antPath)){
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
3.3微服务获取用户
现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?
由于网关发送请求到微服务依然采用的是Http
请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
3.4OpenFeign在不同微服务之间传递用户
由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头
4.网关配置管理
到目前为止我们已经解决了微服务相关的几个问题:微服务远程调用、注册、发现、请求路由、负载均衡、登录用户信息传递,但是还有一些问题:
- 网关路由在配置文件中写死了,如果变更必须重启微服务
- 某些业务配置在配置文件中写死了,每次修改都要重启服务
- 每个微服务都有很多重复的配置,维护成本高
这些问题都可以通过统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能,也具备配置管理的功能:微服务共享的配置可以统一交给Nacos保存和管理,在Nacos控制台修改配置后,Nacos会将配置变更推送给相关的微服务,并且无需重启即可生效,实现配置热更新。
网关的路由同样是配置,因此同样可以基于这个功能实现动态路由功能,无需重启网关即可修改路由配置。
首先在Naocs配置管理/配置列表中添加共同配置
接下来,我们要在微服务拉取共享配置。将拉取到的共享配置与本地的application.yaml
配置合并,完成项目上下文的初始化。
不过,需要注意的是,读取Nacos配置是SpringCloud上下文(ApplicationContext
)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml
。也就是说引导阶段,application.yaml
文件尚未读取,根本不知道nacos 地址,该如何去加载nacos中的配置文件呢?
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml
(或者bootstrap.properties
)的文件,如果我们将nacos地址配置到bootstrap.yaml
中,那么在项目引导阶段就可以读取nacos中的配置了
5.配置热更新
有很多的业务相关参数,将来可能会根据实际情况临时调整。例如购物车业务,购物车数量有一个上限,默认是10,对应如下代码中的count:
private void checkCartsFull(Long userId) {
int count = lambdaQuery().eq(Cart::getUserId, userId).count();
if (count >= 10) {
throw new BizIllegalException(StrUtil.format("用户购物车物品数量不能超过{}", 10));
}
}
现在这里购物车是写死的固定值,我们应该将其配置在配置文件中,方便后期修改。这就要用到Nacos的配置热更新能力了,分为两步:
- 在Nacos中添加配置
- 在微服务读取配置
微服务02-12.配置管理-配置热更新_哔哩哔哩_bilibili
6.动态路由
微服务02-13.配置管理-动态路由(拓展)_哔哩哔哩_bilibili