目录
Spring Cloud Gateway 学习专栏
1. Spring Cloud : Gateway 服务网关认识(一)
2. Spring Cloud :整合Gateway 学习 (二)
3. Spring Cloud:Gateway 路由定义定位器 RouteDefinitionLocator (三)
4. Spring Cloud : Gateway 网关过滤器 GatewayFilter(四)
5. Spring Cloud : Gateway 网关限流(五)
6. Spring Cloud:Gateway 集成 Sentinel (六)
7. Spring Cloud : Gateway Redis动态路由 (七)
8. Spring Cloud : Gateway 整合Swagger (八)
如果发现本文有错误的地方,请大家毫不吝啬,多多指教,欢迎大家评论,谢谢!
一、概述
本篇文章为系列文章,未读第 上一集 的同学请猛戳这里: Spring Cloud :整合Gateway 学习 (二),下面是讲解网关过滤器 GatewayFilter 使用
二、过滤器
Spring Cloud Gateway 根据作用范围划分为 GatewayFilter
和 GlobalFilter
,二者区别如下:
GatewayFilter
:网关过滤器,需要通过spring.cloud.routes.filters
配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters
配置在全局,作用在所有路由上。GlobalFilter
:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter
包装成GatewayFilterChain
可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
二、网关过滤器 GatewayFilter
网关过滤器用于拦截并链式处理 Web 请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。修改传入的 HTTP 请求或传出 HTTP 响应。Spring Cloud Gateway 包含许多内置的网关过滤器工厂一共有 22 个,包括头部过滤器、 路径类过滤器、Hystrix 过滤器和重写请求 URL 的过滤器, 还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。
接下来我们举例说明其中一部分如何使用,其余等大家工作中需要应用时再查询资料学习或者咨询我也可以。
1. Path 路径过滤器
Path 路径过滤器可以实现 URL 重写,通过重写 URL 可以实现隐藏实际路径提高安全性,易于用户记忆和键入,易于被搜索引擎收录等优点。实现方式如下:
RewritePathGatewayFilterFactory
RewritePath 网关过滤器工厂采用路径正则表达式参数和替换参数,使用 Java 正则表达式来灵活地重写请求路径。
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 断言,路径相匹配的进行路由 (谓词)
- Path=/user/** # 路径匹配
filters:
#将 /user/user/getUserInfo?userId=1 重写为 /user/getUserInfo?userId=1
- RewritePath=/user/(?<segment>.*),/$\{segment}
访问:http://127.0.0.1:9000/user/user/getUserInfo?userId=1 结果如下:
PrefixPathGatewayFilterFactory
PrefixPath 网关过滤器工厂为匹配的 URI 添加指定前缀
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 断言,路径相匹配的进行路由 (谓词)
- Path=/** # 所有路径都匹配
filters:
# 将 /user/getUserInfo?userId=1 重写为 /user/user/getUserInfo?userId=1
- PrefixPath=/user
访问:http://127.0.0.1:9000/user/getUserInfo?userId=1 结果如下:
StripPrefixGatewayFilterFactory
StripPrefix 网关过滤器工厂采用一个参数 StripPrefix,该参数表示在将请求发送到下游之前从请求中剥离的路径个数。
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 断言,路径相匹配的进行路由 (谓词)
- Path=/** # 所有路径都匹配
filters:
# 将 /api/user/getUserInfo?userId=1 重写为 user/getUserInfo?userId=1
- StripPrefix=1
访问:http://127.0.0.1:9000/api/user/getUserInfo?userId=1 结果如下:
SetPathGatewayFilterFactory
SetPath 网关过滤器工厂采用路径模板参数。 它提供了一种通过允许模板化路径段来操作请求路径的简单方法,使用了 Spring Framework 中的 uri 模板,允许多个匹配段。
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 匹配对应 URI 的请求,将匹配到的请求追加在目标 URI 之后
- Path=/api/user/{segment}
filters:
# 将 /api/user/getUserInfo?userId=1 重写为 /user/getUserInfo?userId=1
- SetPath=/user/{segment}
访问:http://127.0.0.1:9000/api/user/getUserInfo?userId=1 结果如下:
2. Parameter 参数过滤器
AddRequestParameter
网关过滤器工厂会将指定参数添加至匹配到的下游请求中。
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 匹配对应 URI 的请求,将匹配到的请求追加在目标 URI 之后
- Path=/api/user/{segment}
filters:
# 将 /user/user/getUserInfo?userId=1 重写为 /user/getUserInfo?userId=1
- RewritePath=/user/(?<segment>/?.*), $\{segment}
# 在下游请求中添加 flag=1
- AddRequestParameter=flag, 1
修改用户服务的控制层代码
添加一个 getUserParam 获取网关请求自定义参数接口
@GetMapping("getUserParam")
@ApiOperation(value = "获取用户参数信息")
public UserResp getUserParam(HttpServletRequest request,
@RequestParam("userId") @ApiParam(name = "userId",value = "用户ID") Long userId
) {
String foo = request.getParameter("foo");
log.info("getUserHead.resp foo={}",foo);
UserResp userResp = UserResp.builder()
.id(userId)
.createTime(new Date())
.nickname("change")
.username("Zou.LiPing")
.roleName("超级管理员")
.build();
return userResp;
}
访问:http://127.0.0.1:9000/user/user/getUserParam?userId=1 控制台结果如下:
UserController : getUserHead.resp foo=bar
3. Status 状态过滤器
SetStatus 网关过滤器工厂采用单个状态参数,它必须是有效的 Spring HttpStatus。它可以是整数 404 或枚举 NOT_FOUND 的字符串表示。
server:
port: 9000
service-url:
user-service: http://localhost:8200
spring:
application:
name: mall-gateway
main:
allow-bean-definition-overriding: true
## redis配置
redis:
database: 0
host: 47.103.20.21
password: zlp123456
port: 6379
timeout: 7000
cloud:
## nacos注册中心
nacos:
discovery:
server-addr: 47.103.20.21:8848
## gateway配置
gateway:
routes:
- id: user #路由的ID 保证唯一
uri: ${service-url.user-service} # 目标服务地址:uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
predicates:
# 匹配对应 URI 的请求,将匹配到的请求追加在目标 URI 之后
- Path=/api/user/{segment}
filters:
# 将 /user/user/getUserInfo?userId=1 重写为 /user/getUserInfo?userId=1
- RewritePath=/user/(?<segment>/?.*), $\{segment}
# 任何情况下,响应的 HTTP 状态都将设置为 404
- SetStatus=404 # 404 或者对应的枚举 NOT_FOUND
访问:http://127.0.0.1:9000/user/user/getUserParam?userId=1 控制台结果如下:
三、全局过滤器 GlobalFilter
全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它是请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
1. 自定义过滤器
即使
Spring Cloud Gateway
自带许多实用的GatewayFilter Factory、Gateway Filter、Global Filter
,但是在很多情景下我们仍然希望可以自定义自己的过滤器,实现一些骚操作。
2. 自定义网关过滤器
自定义网关过滤器需要实现以下两个接口 :
GatewayFilter
,Ordered
。
创建过滤器
CustomGatewayFilter.java
@Slf4j
@Component
public class CustomGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("自定义网关过滤器...");
return chain.filter(exchange);// 继续向下执行
}
@Override
public int getOrder() {
return 0;
}
}
注册过滤器
package com.example.config;
import com.example.filter.CustomGatewayFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 网关路由配置类
*/
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r
// 断言(判断条件)
.path("/product/**")
// 目标 URI,路由到微服务的地址
.uri("lb://ai-product")
// 注册自定义网关过滤器
.filters(new CustomGatewayFilter())
// 路由 ID,唯一
.id("product"))
.build();
}
}
访问
注释配置文件中所有网关配置,重启并访问:http://localhost:9000/product/getProduct?productId=1 控制台结果如下:
自定义网关过滤器被执行
3. 统一鉴权
接下来我们在网关过滤器中通过 token 判断用户是否登录,完成一个统一鉴权案例。
AuthorizeFilter
/**
* 认证过滤器
* @date: 2021/4/22 19:30
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpResponse serverHttpResponse = exchange.getResponse();
// 获取 token 参数
String token = serverHttpRequest.getHeaders().getFirst("token");
if (StringUtils.isBlank(token)) {
serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
return getVoidMono(serverHttpResponse, ResultCode.TOKEN_MISSION);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
/**
* 错误信息响应到客户端
* @param serverHttpResponse Response
* @param resultCode 错误枚举
* @date: 2021/4/20 9:13
* @return: reactor.core.publisher.Mono<java.lang.Void>
*/
private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ResultCode resultCode) {
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSON.toJSONString(Result.failed(resultCode)).getBytes());
return serverHttpResponse.writeWith(Flux.just(dataBuffer));
}
}
访问
重启并访问:http://localhost:9000/product/getProduct?productId=1
我在头部请求 token 参数
源码地址
mall-gateway 这个项目
https://gitee.com/gaibianzlp/zlp-mall-demo.git
参考链接
1. Spring Cloud 系列之 Gateway 服务网关