一、路由断言
- 路由断言就是判断路由转发的规则
二、路由过滤器
- 路由过滤器可以实现对网关请求的处理,可以使用 Gateway 提供的,也可以自定义过滤器
- 路由过滤器 GatewayFilter(默认不生效,只有配置到路由后才会生效):
-
只对一个路由生效,配置到 routes 下,和 predicates 同级
-
对所有路由生效:配置到 default-filters
- 全局过滤器 GlobalFilter(所有路由都生效):声明后自动生效
三、网关请求处理流程
业务实战:如果要进行用户校验,应该在网关请求转发之前先完成对用户的校验,判断用户是否有请求权限
- 网关请求转发:NettyRoutingFilter 的 PRE 阶段
做法:
在 NettyRoutingFilter 之前的过滤器链中自定义过滤器完成登录校验
在自定义过滤器 PRE 阶段完成登录校验(POST 阶段请求已经转发了,不符合业务:先登录才能请求)
网关中没有业务逻辑,只是做路由转发,网关进行登录校验之后获取用户信息并没有实际作用(除了校验)
网关将请求转发到微服务,微服务需要用户信息!网关需要将用户信息传给微服务
怎么传?:网关将请求转发到微服务,其实就是发起了一次 HTTP 请求,可以将用户信息保存到请求头,微服务从请求头中取出用户信息,且不会对业务造成影响 ✔
微服务之间的调用怎么获取用户信息?:网关只将用户信息传递给了一个微服务(cart-service),但是微服务之间会相互调用(trade-service 调用 cart-service)trade-service 并没有用户信息,cart-service 也不会自动将用户信息传递给调用它的微服务
- 可以使用将用户信息保存到请求头的方式在微服务之间传递用户信息吗
- 微服务之间的请求(OpenFeign)和网关与微服务之间(Spring Cloud Gateway 内置的)的请求不同,实现方式有差别
四、网关统一实现用户登录校验(基于 JWT)
1. 自定义过滤器并控制过滤器的顺序
-
路由过滤器GatewayFilter(默认不生效,只有配置到路由后才会生效):
-
只对一个路由生效,配置到 routes 下,和 predicates 同级
-
对所有路由生效:配置到 default-filters
-
- 全局过滤器 GlobalFilter(所有路由都生效):声明后自动生效
filter 方法解读:
-
参数 1 ServerWebExchange exchange:网关内部的上下文对象,保存网关内部整个过滤器链的共享数据,如 request、response、session 及 一些自定义的共享属性,所有过滤器都可以从 exchange 中读取 / 存储共享数据
-
参数 2 GatewayFilterChain chain:过滤器链,当前过滤器执行完之后,通过 chain 调用过滤器链中的下一个过滤器
网关采用的是非阻塞式的编程,利用 Mono 定义回调函数,等之后请求转发回来的返回结果有了再调用回调函数(过滤器中 POST 部分的逻辑),一般不用写回调
e.g 实际开发中遇到的回调:接口调用成功后统计接口调用次数 + 1,就是要等到返回结果是 ok 的再去执行调用次数 + 1 的逻辑!
- 返回值 Mono<Void>:每个过滤器执行完之后直接返回 Mono
自定义过滤器的优先级比 NettyRoutingFilter 高即可,NettyRoutingFilter 的 order 值是 maxInt,优先级最低
java
// 1. 实现 GlobalFilter 接口中的 filter 方法
// 2. 实现 Orderd 接口的 getOrder 方法,通过 ORDER 控制过滤器优先级
// 3. 放行(将 exchange 传到下一个过滤器)
2. 在网关实现登录校验
-
获取请求头
-
判断请求路径是否需要拦截(有些路径不需要登录也可以查看)
使用 Spring 提供的 AntPathMatcher 的 match() 方法来校验路径是否匹配指定的路径模式 pathPattern(/search/** 等路径)
java
private final AntPathMatcher antPathMatcher = newAntPathMatcher();
-
获取 token
-
校验并解析 token
-
拦截未登录用户
-
设置响应状态码:未登录 401
-
终止流程
-
3. 网关传递用户信息给下游微服务
-
传递
-
使用上下文对象中修改请求的 API:mutate()
-
将构造好的新的上下文对象传给下一个过滤器
-
- 获取:微服务中的很多业务都需要获取用户信息,将从请求头中获取用户信息的逻辑写到拦截器 中,统一获取;可能会有很多个微服务,写拦截器要在所有微服务里都写一遍?=> 写到公共模块 common 其他微服务都引入这个模块(依赖),就都有了拦截器的功能 ✔
-
定义拦截器
-
实现 HandlerInterceptor 接口其中的 preHandle() 方法,在 Controller 之前执行
-
实现 afterCompletion() 方法在 Controller 执行完之后执行,清楚 ThreadLocal 中的用户信息
-
-
注册拦截器:定义 SpringMVC 的配置类,实现 WebMvcConfigurer 中的 addInterceptors()
-
扫描包:common 模块和微服务模块所在包不同,Spring 不会自动扫描到配置类 ,需要自己配置,实现 Spring 自动装配
-
保存到 ThreadLocal 中,其他业务从 ThreadLocal 取出用户信息
-
注意❗网关模块不能使用 / 引用 SpringMVC 相关的类,Spring Cloud Gateway 的底层用的不是 SpringMVC,而是响应式的 webflux
网关项目也引入了 common 模块,但是 common 模块中定义了 SpringMVC 的拦截器,微服务项目需要该拦截器,网关不需要该拦截器,如何次拦截器配置类在有些情况(微服务模块)生效,有些情况(网关)不生效?
解决方法:让 SpringMVC 的配置类根据条件来加载,即判断是否有 SpringMVC(网关和其他微服务项目的差别就是是否有 SpringMVC,可以根据是否有 SpringMVC 中的核心 API DispatcherServlet)
实现:使用 Spring 的条件注解 @ConditionalOnClass(DispatcherServlet.class)
4. 微服务之间的信息传递(OpenFeign 传递用户)
用户信息是从 ThreadLocal 中取的,要先从请求头中获取用户信息才能存到 ThreadLocal 中,而请求头中的用户信息是网关向微服务发起请求时添加的,但是微服务之间的是直接调用的,没有经过网关,而是通过 OpenFeign 的远程调用,如何修改 OpenFeign 发起的请求?
-
OpenFeign 中提供了一个拦截器接口 RequestInterceptor,所有由 OpenFeign 发起的请求(远程调用)都会先调用拦截器处理请求
-
其中的 RequestTemplate 类中提供了一些方法(如 header())可以让我们修改请求头