网关过滤器使用及其原理分析

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;
            }
  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
要修改请求参数,可以在网关过滤器使用Spring Cloud Gateway提供的请求修改工具类ServerWebExchangeDecorator,具体步骤如下: 1. 注入ServerWebExchangeDecoratorFactory类型的bean: ```java @Bean public ServerWebExchangeDecoratorFactory serverWebExchangeDecoratorFactory() { return new MyServerWebExchangeDecoratorFactory(); } ``` 2. 实现ServerWebExchangeDecoratorFactory接口,返回自定义的ServerWebExchangeDecorator: ```java public class MyServerWebExchangeDecoratorFactory implements ServerWebExchangeDecoratorFactory { @Override public ServerWebExchangeDecorator decorate(ServerWebExchange exchange) { return new MyServerWebExchangeDecorator(exchange); } } ``` 3. 实现自定义的ServerWebExchangeDecorator,通过修改ServerHttpRequest对象中的请求参数来修改请求参数。例如,将请求中的name参数改为"world": ```java public class MyServerWebExchangeDecorator extends ServerWebExchangeDecorator { public MyServerWebExchangeDecorator(ServerWebExchange delegate) { super(delegate); } @Override public ServerHttpRequest getRequest() { ServerHttpRequest request = super.getRequest(); MultiValueMap<String, String> queryParams = request.getQueryParams(); if (queryParams.containsKey("name")) { queryParams.put("name", Arrays.asList("world")); request = new ServerHttpRequestDecorator(request) { @Override public MultiValueMap<String, String> getQueryParams() { return queryParams; } }; } return request; } } ``` 以上就是在网关过滤器中修改请求参数的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lm_ylj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值