SpringCloudGateway自定义Filter

young 663 2022-05-27

使用Gateway的时候,时长会有一些需求,要放在Gateway中去处理,如果一些登陆校验,权限校验之类的,从而降低一些应用服务器的压力。这个时候就需要在Gateway项目中自定义Filte去处理。

步骤

创建自定义filter的步骤

  1. 创建XxxGatewayFilterFactory,继承AbstractGatewayFilterFactory,如CheckLoginGatewayFilterFactory,必须以GatewayFilterFactory为结尾

  2. 重写apply方法

  3. 通过@Bean的方式将自定义的Filter添加到spring容器中,在类上加@Service的方式无法添加

  4. 在配置中引入自定义Filter,名称为XxxGatewayFilterFactory中的Xxx,如CheckLogin,

  5. 如果需要增加一些配置,用于Filter的逻辑判断,如官方的PrefixPathGatewayFilte中可以配置prefilx的值,那么可以在当前类创建一个静态内部类,然后声明在类上,如

public class RejectUrlGatewayFilterFactory extends AbstractBaseCheckGatewayFilterFactory<RejectUrlGatewayFilterFactory.Config> 
  1. 写入到配置文件中,由于我们不引入Order,所以默认我们写的自定义Filter的Order都是0,引入多个filter时,执行顺序按配置的顺序执行

示例

普通的自定义filter

比如校验请求的header中是否包含必须的自定义的参数

创建CheckHeaderParamGatewayFilterFactory,然后创建一个Config,用来配置需要被校验的header,

然后继承AbstractGatewayFilterFactory,重写applay方法,apply方法中的Config参数就是我们自己定义的Config

public class CheckHeaderParamGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckHeaderParamGatewayFilterFactory.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        return null;
    }

    @Data
    public static class Config {
        /**
         * 要校验的header的key,用逗号分隔
         */
        private String header;
    }
}

编写处理逻辑

@Slf4j
public class CheckHeaderParamGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckHeaderParamGatewayFilterFactory.Config> {

    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String[] checkHeaderArr = getCheckHeaderArr(config);
          	// 没有配置,进入下一个filter
            if (checkHeaderArr == null){
                log.info("check header config is null");
                return chain.filter(exchange);
            }
            log.info("requestUri:【{}】 校验 header", exchange.getRequest().getURI().toString());
          	// 获取请求的所有header
            HttpHeaders headers = exchange.getRequest().getHeaders();
            for (String headerName : checkHeaderArr) {
                if (StringUtils.isBlank(headerName)){
                    continue;
                }
              	// 如果要被校验的header不存在,直接编写响应信息给接口调用方
                if (!headers.containsKey(headerName)) {
                    return writeParamErrorResponse(exchange, headerName + "不能为空");
                }
            }
            log.info("requestUri:【{}】 校验 header 通过", exchange.getRequest().getURI().toString());
          	// 校验通过,进入下一个filter
            return chain.filter(exchange);
        };
    }

  	/**
  	 * 处理失败时的返回
  	 */
    private Mono<Void> writeParamErrorResponse(ServerWebExchange exchange, String message) {
        ApiResponse<Void> response = new ApiResponse<>();
        response.setResultCode("PA_PARAM_ERROR");
        response.setMessage(message);
        return writeErrorResponse(exchange, response);
    }

  	/**
  	 * 处理失败时的返回
  	 */
    private Mono<Void> writeErrorResponse(ServerWebExchange exchange, Object responseBody) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        response.setStatusCode(HttpStatus.OK);
        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                log.info("校验失败:{}", responseBody);
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(responseBody));
            } catch (JsonProcessingException e) {
                log.error("json转换异常", e);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
	
  	/**
  	 * 从Config中获取配置
  	 */
    private String[] getCheckHeaderArr(Config config) {
        String[] checkHeaderArr;
        String header = config.getHeader();
        if (StringUtils.isBlank(header)) {
            checkHeaderArr = null;
        } else {
            checkHeaderArr = header.split(",");
        }
        return checkHeaderArr;
    }

    @Data
    public static class Config {
        /**
         * 要校验的header的key,用逗号分隔
         */
        private String header;
    }
}

将filter注册到Spring容器中

@Configuration
public class GatewayFilterConfig {

    @Bean
    public CheckHeaderParamGatewayFilterFactory checkHeaderParamGatewayFilterFactory(){
        return new CheckHeaderParamGatewayFilterFactory();
    }
}

在配置文件中配置的模式

yaml

filters:
        - CheckHeaderParam=token,visitorId

本人项目中采用在nacos中通过json的模式进行动态路由的配置,json中的对应格式为

"filters": [
  {
    "name": "CheckHeaderParam",
    "args":{
      	"header":"token,visitorId"
    }
  }
]

继承了Spring中某些特殊filter

比如某些场景下,我们需要对某个路由中的响应做数据类型转换,这个时候,可以直接继承Spring的ModifyResponseBodyGatewayFilterFactory

@Slf4j
public class ConvertInsurBankResponseGatewayFilterFactory extends ModifyResponseBodyGatewayFilterFactory {
    public ConvertInsurBankResponseGatewayFilterFactory(List<HttpMessageReader<?>> messageReaders, Set<MessageBodyDecoder> messageBodyDecoders, Set<MessageBodyEncoder> messageBodyEncoders) {
        super(messageReaders, messageBodyDecoders, messageBodyEncoders);
    }

    @Autowired
    private InsurBankCodeMappingConfig insurBankCodeMappingConfig;

    @Override
    public GatewayFilter apply(Config config) {
      	// 创建一个新的Config
        Config conf = new Config();
      	// 处理类型转换
        conf.setRewriteFunction(InsurBankResponse.class, ApiResponse.class,(serverWebExchange, response) -> {
            Optional<InsurBankResponse> resp = Optional.ofNullable(response);
            ApiResponse<Object> apiResponse = new ApiResponse<>();
            apiResponse.setBody(resp.map(InsurBankResponse::getData).orElse(null));
            apiResponse.setResultCode(resp.map(InsurBankResponse::getCode).map(e->insurBankCodeMappingConfig.getCodeMapping(e)).orElse(StringUtils.EMPTY));
            apiResponse.setMessage(resp.map(InsurBankResponse::getMsg).orElse(StringUtils.EMPTY));
            log.info("InsurBankResponse:【{}】, ApiResponse:【{}】",response,apiResponse);
            return Mono.just(apiResponse);
        });
      	// 将新创建的config传入
        return super.apply(conf);
    }
}

添加到Spring容器

@Bean
public ConvertInsurBankResponseGatewayFilterFactory convertInsurBankResponseGatewayFilterFactory(ServerCodecConfigurer codecConfigurer, Set<MessageBodyDecoder> bodyDecoders,
                                                                                                Set<MessageBodyEncoder> bodyEncoders) {
    return new ConvertInsurBankResponseGatewayFilterFactory(codecConfigurer.getReaders(), bodyDecoders, bodyEncoders);
}

在配置文件中配置的模式

yaml

filters:
 - ConvertInsurBankResponse

json

"filters": [
  {
    "name": "ConvertInsurBankResponse",
  }
]