如何使用 Spring Cloud Gateway 自定义过滤器过滤每个请求?

Posted

技术标签:

【中文标题】如何使用 Spring Cloud Gateway 自定义过滤器过滤每个请求?【英文标题】:How to use a Spring Cloud Gateway Custom Filter to filter every request? 【发布时间】:2020-11-14 18:08:19 【问题描述】:

这是我第一次实现 Spring Cloud Gateway。

我需要过滤每个请求并在某些路径上应用过滤器验证。在Baeldung Custom Filters tutorial 之后,我做了一个简单的应用程序来过滤请求。

应用程序必须发布/actuator/health 之类的路径,并验证后端服务的特定路径。到目前为止,我已经实现了GlobalFilterGatewayFilterFactory。每个请求都会调用全局过滤器,但在应用程序启动时只调用一次 GatewayFilter,这样我就无法为每个请求创建身份验证逻辑。 auth 逻辑是关于一个特定的头字段。所以,我的具体问题是:

    如何使用特定路径验证每个请求? 如何拒绝请求并发送错误信息?

全局过滤器

@Component
public class LoggingGlobalPreFilter implements GlobalFilter 

    final Logger LOGGER = LoggerFactory.getLogger(LoggingGlobalPreFilter.class);

    @Override
    public Mono<Void> filter(
            ServerWebExchange exchange,
            GatewayFilterChain chain) 
        LOGGER.info("Global Pre Filter executed");
        return chain.filter(exchange);
    


网关过滤器

@Component
public class LoggingGatewayFilterFactory extends
        AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> 

    final Logger LOGGER =
            LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);

    public LoggingGatewayFilterFactory() 
        super(Config.class);
    

    private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus)  
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        return response.setComplete();
    

    private boolean isAuthorizationValid(String authorizationHeader) 
        boolean isValid = true;
        return authorizationHeader.equals("x-header");
    

    @Override
    public GatewayFilter apply(Config config) 
        LOGGER.info("M=apply, Msg=Applying Gateway Filter....");
        return ((exchange, chain) -> 
            LOGGER.info("M=apply, Msg=Applying Gateway Filter...."); // APARENTELLY NEVER ENTER HERE.
            ServerHttpRequest request = exchange.getRequest();

            if (!request.getHeaders().containsKey(TsApiGatewayConstants.HEADER_APIKEY)) 
                return this.onError(exchange, TsApiGatewayConstants.MESSAGE_API_KEY_MISSING, HttpStatus.UNAUTHORIZED);
            

            String apiKey = request.getHeaders().get(TsApiGatewayConstants.HEADER_APIKEY).get(0);
            String userAgent = request.getHeaders().get(TsApiGatewayConstants.HEADER_USER_AGENT).get(0);

            if (!this.isAuthorizationValid(userAgent)) 
                return this.onError(exchange, TsApiGatewayConstants.MESSAGE_API_KEY_INVALID, HttpStatus.UNAUTHORIZED);
            

            return chain.filter(exchange);
        );
    

    public static class Config 
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;

        public Config(String baseMessage, boolean preLogger, boolean postLogger) 
            this.baseMessage = baseMessage;
            this.preLogger = preLogger;
            this.postLogger = postLogger;
        

        public String getBaseMessage() 
            return baseMessage;
        

        public void setBaseMessage(String baseMessage) 
            this.baseMessage = baseMessage;
        

        public boolean isPreLogger() 
            return preLogger;
        

        public void setPreLogger(boolean preLogger) 
            this.preLogger = preLogger;
        

        public boolean isPostLogger() 
            return postLogger;
        

        public void setPostLogger(boolean postLogger) 
            this.postLogger = postLogger;
        
    

application.yml

  cloud:
    gateway:
      routes:
      - id: service_route
        uri: https://backend-url:443
        predicates:
          - Path=/api
        filters:
         - Logging

过滤器的示例路径:https://backend-url:443/api/service1

【问题讨论】:

我面临同样的问题。我们能解决这个问题吗? @SasukeUchiha 请看下面我的解决方案。 请不要在问题标题中添加SOLVED 之类的内容。如果您的问题已解决,请接受帮助您解决问题的答案,或者 - 如果没有合适的答案 - 发布您自己的答案并在超时后接受。我已恢复将[SOLVED] 添加到标题的编辑。 【参考方案1】:

我找到了解决它的方法。我使用了一个 RouteConfiguration 组件来设置路由和一个 GatewayFilter 类。在 RouteConfiguration 的 Bean 上,我已将特定过滤器设置为路由路径。就我而言,我使用了过滤器进行身份验证。

网关过滤器

@RefreshScope
@Component
public class AuthenticationFilter implements GatewayFilter 

    final Logger LOGGER = LoggerFactory.getLogger(AuthenticationFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) 
        ServerHttpRequest request = exchange.getRequest();

// Make your business logic, this is a simple sample.


        if (!request.getHeaders().containsKey("x-api-key")) 
            return this.onError(exchange,"api-key missing",HttpStatus.FORBIDDEN);
        

        return chain.filter(exchange); // Forward to route
    

    private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus)  
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        return response.setComplete();
    

路线配置

@RefreshScope
@Configuration
public class RouteConfiguration 

    @Value("$routes.api")
    private String apiHost;

    @Autowired
    AuthenticationFilter authenticationFilter;

    @Bean
    public RouteLocator apiRoutes(RouteLocatorBuilder builder) 
        return builder.routes()
                .route("CHOICE A ROUTE ID",p -> p
                        .path("/api/**")
                        .filters(f -> f
                                .filter(authenticationFilter) // You can add more filters here.
                                .stripPrefix(1))
                        .uri(apiHost))
                .build();
    


【讨论】:

【参考方案2】:

如果你想验证每个请求,你应该实现 Ordered 接口和 并返回 -2; 例如:

     @Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered 
    @Override
    public int getOrder() 
        // -1 is response write filter, must be called before that
        return -2;
    

拒绝请求并发送错误消息 你可以看到这个 .use MonoUtil.buildServerResponse 方法。

MonoUtil

【讨论】:

以上是关于如何使用 Spring Cloud Gateway 自定义过滤器过滤每个请求?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Cloud Gateway 中添加特定于路由的自定义过滤器

如何使用 Spring Cloud Gateway 自定义过滤器过滤每个请求?

如何在提交之前修改 Spring Cloud Gateway 中的响应正文

如何使用Spring Cloud Gateway,99%的人都不知道!

如何在 spring-cloud-gateway 合约测试中从 spring-cloud-contract 中设置带有 StubRunner 端口的 url

Spring Cloud Gateway 初体验