1.网关过滤器介绍
网关过滤器的用途一般是修改请求或响应信息,例如编解码、Token验证、流量复制等
官方文档地址:Spring Cloud Gateway
网关过滤器分为GloablFilter、GatewayFilter及DefaultFilter
过滤器的执行顺序由Order决定,Order值越小,优先级越高,越先执行
1.1 GlobalFilter
自定义的GlobalFilter一般需要实现GlobalFilter和Order接口,官网的示例代码如下:
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
配置了全局过滤器之后,当接收到匹配路由的请求时,会执行过滤
1.2 DefaultFilter
DefaultFilter也可以作用于所有路由,配置方式如下:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=X-Request-Id,123456
1.3 GatewayFilter
官方提供了许多现成的GatewayFilter,可以直接使用,具体参考官方文档,RewritePath示例如下:
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: https://example.org
predicates:
- Path=/red/**
filters:
- RewritePath=/red/?(?<segment>.*), /$\{segment}
很多场景下需要自定义GatewayFilter,一般可以自定义GatewayFilterFactory继承自AbstractGatewayFilterFactory,名称必须由filter名称+GatewayFilterFactory组成,然后再实现apply方法中返回自定义的GatewayFilter对象,该对象一般需要实现GatewayFilter及Order接口,示例如下:
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> {
public CustomGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public GatewayFilter apply(CustomConfig config) {
return new CustomGatewayFilter(config);
}
}
@Getter
@Setter
public class CustomConfig {
private String desc;
}
@Slf4j
public class CustomGatewayFilter implements GatewayFilter, Ordered {
// 配置类,对应yaml中的args部分
private CustomConfig config;
public CallbackGatewayFilter(CallbackConfig config) {
this.config = config;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 执行过滤逻辑
log.info("custom gateway filter:{}", config.getDesc);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
在配置文件中使用时,通过name属性指定filter名称,可以通过args传入自定义配置参数,这些参数会被封装到配置类CustomConfig中,例如:
spring:
cloud:
gateway:
routes:
- id: oldServer
uri: lb://oldServer
predicates:
- Path=/MyServer/**
filters:
# 通过name+GatewayFilterFactory找到对应的过滤器
- name: Custom
# 自定义过滤器配置
args:
desc: "this is a custom filter"
2.实际开发中的应用
2.1 缓存请求体全局过滤器
添加缓存请求体的全局过滤器,作用是避免后续获取body数据时报错Only one connection receive subscriber allowed,因为原始的body数据只能被订阅读取一次
@Slf4j
@Component
public class CacheRequestBodyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
long contentLength = headers.getContentLength();
Optional<String> chunked = headers.toSingleValueMap().entrySet().stream()
.filter(entry -> entry.getKey().equalsIgnoreCase(HttpHeaders.TRANSFER_ENCODING.toLowerCase()))
.map(Map.Entry::getValue)
.filter(value -> value.equalsIgnoreCase("chunked"))
.findFirst();
if (contentLength > 0 || chunked.isPresent()) {
return readBody(exchange, chain);
}
return filterExchange(exchange, chain);
}
private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
// Flux.defer()延迟创建Flux,直到有订阅者时才创建
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
// 添加到exchange的requestBody属性中
exchange.getAttributes().put("requestBody", new String(bytes));
return Mono.just(buffer);
});
// 构建ServerHttpRequest的装饰器,重写getBody方法,避免出现body只能获取一次的问题
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
// 修改exchange中的请求对象
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return filterExchange(mutatedExchange, chain);
});
}
@Override
public int getOrder() {
// 最高优先级
return HIGHEST_PRECEDENCE;
}
private static Mono<Void> filterExchange(ServerWebExchange exchange, GatewayFilterChain chain) {
return ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders())
.bodyToMono(String.class)
.doOnNext(objectValue -> log.info("[GatewayContext]Read body Success"))
.then(chain.filter(exchange));
}
}
2.2 请求加解密过滤器
在实际开发中,网关接收到的请求内容时常是加密的,需要解密,而响应的内容有时需要加密,这种场景下可以通过自定义网关过滤器实现
1)创建加解密过滤器工厂类
@Component
public class EncryptDecryptGatewayFilterFactory extends AbstractGatewayFilterFactory<EncryptDecryptConfig> {
public EncryptDecryptGatewayFilterFactory() {
super(EncryptDecryptConfig.class);
}
@Override
public GatewayFilter apply(EncryptDecryptConfig config) {
return new EncryptDecryptFilter(config);
}
}
2)创建加解密过滤器
@Slf4j
public class EncryptDecryptFilter implements GatewayFilter, Ordered {
private final EncryptDecryptConfig config;
public EncryptDecryptFilter(EncryptDecryptConfig config) {
this.config = config;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 创建一个表达式对象
ExchangeExpression expression = new ExchangeExpression(exchange);
// 如果没有配置待解密表达式,则无需解密
List<String> encodedFieldList = config.getEncodedField();
if (!CollectionUtils.isEmpty(encodedFieldList)) {
String encodeData = null;
for (String encodedField : encodedFieldList) {
// 执行表达式,获取待解密的字符串
encodeData = (String) expression.evalNoException(encodedField);
if (!StringUtils.isEmpty(encodeData)){
break;
}
}
String bodyData;
if (StringUtils.hasText(encodeData)) {
// 执行解密,这里使用最简单的Base64进行加解密,可以根据实际情况换成其他的加解密算法,这里仅演示网关的作用
bodyData = Base64.decodeStr(encodeData);
// 假设请求体是JSON格式,获取解密后的内容,设置到上下文中,这样后面的过滤器可以方便使用
Map<String, Object> bodyMap = JSONUtil.parseObj(bodyData);
for (Map.Entry<String, Object> entry : bodyMap.entrySet()) {
exchange.getAttributes().put(entry.getKey(), entry.getValue());
}
// 重新构造解密后的exchange内容
ExchangeConfig exchangeConfig = ExchangeConfig.builder()
.bodyObject(bodyData)
.bodyContentType(config.getRequestContentType())
.header((Map<String, String>) expression.getField("header"))
.param((Map<String, String>) expression.getField("param"))
.build();
exchange = ServerWebExchangeUtil.rebuildRequest(exchange, exchangeConfig);
}
}
// 根据配置判断响应是否需要加密
boolean encrypt = Boolean.TRUE.equals(config.getCrypt());
if (encrypt) {
// 如果需要加密,则修改响应内容
return chain.filter(exchange.mutate().response(buildResponse(exchange)).build());
}
// 执行下一个过滤器
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 设置过滤器执行顺序,解密过滤器一般优先级最高,order必须小于-1,否则writeWith方法不生效,原因是NettyWriteResponseFilter的order为-1
return config.getOrder() == null ? Integer.MIN_VALUE + 1 : config.getOrder();
}
private ServerHttpResponseDecorator buildResponse(ServerWebExchange exchange) {
ServerHttpResponse originalResponse = exchange.getResponse();
return new ServerHttpResponseDecorator(originalResponse) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = super.getHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return headers;
}