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

huangapple 未分类评论43阅读模式
英文:

How to use a Spring Cloud Gateway Custom Filter to filter every request?

问题

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

我需要过滤每个请求并在某些路径上应用过滤器验证。根据[Baeldung自定义过滤器教程][1],我创建了一个简单的应用程序来过滤请求。

该应用程序必须释放像`/actuator/health`这样的路径,并验证指定的路径到后端服务。到目前为止,我已经实现了一个`GlobalFilter`和一个`GatewayFilterFactory`。全局过滤器在每个请求时都会被调用,但是GatewayFilter只在应用程序启动时调用一次,因此我无法为每个请求执行身份验证逻辑。身份验证逻辑涉及特定的标头字段。因此,我关注的问题是:

1. 如何对每个请求进行特定路径的验证?
2. 如何拒绝请求并发送错误消息?

**GlobalFilter**

```java
@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);
    }

}

GatewayFilter

@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...."); // 显然从未进入此处。
            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


<details>
<summary>英文:</summary>

It&#39;s my first time at Spring Cloud Gateway implementation. 

I need filter every request and apply a filter validation on some paths. Following the  [Baeldung Custom Filters tutorial][1] I make a simple application to filter requests.

The application must release paths like `/actuator/health` and validate specific paths to backend service. So far, I&#39;ve implemented a `GlobalFilter` and a `GatewayFilterFactory`. The Global filter is called every request but the GatewayFilter is called just once when application starts, that way I can&#39;t make the auth logic to every request. The auth logic is about a specific header field. So, my grained questions are:

1. How validate every request with a specific path?
2. How refuse a request and send a error message?

**GlobalFilter**

    @Component
    public class LoggingGlobalPreFilter implements GlobalFilter {
    
        final Logger LOGGER = LoggerFactory.getLogger(LoggingGlobalPreFilter.class);
    
        @Override
        public Mono&lt;Void&gt; filter(
                ServerWebExchange exchange,
                GatewayFilterChain chain) {
            LOGGER.info(&quot;Global Pre Filter executed&quot;);
            return chain.filter(exchange);
        }
    
    }

**GatewayFilter**

    @Component
    public class LoggingGatewayFilterFactory extends
            AbstractGatewayFilterFactory&lt;LoggingGatewayFilterFactory.Config&gt; {
    
        final Logger LOGGER =
                LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);
    
        public LoggingGatewayFilterFactory() {
            super(Config.class);
        }
    
        private Mono&lt;Void&gt; 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(&quot;x-header&quot;);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            LOGGER.info(&quot;M=apply, Msg=Applying Gateway Filter....&quot;);
            return ((exchange, chain) -&gt; {
                LOGGER.info(&quot;M=apply, Msg=Applying Gateway Filter....&quot;); // 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

Example path to filter: `https://backend-url:443/api/service1`



  [1]: https://www.baeldung.com/spring-cloud-custom-gateway-filters

</details>


# 答案1
**得分**: 6

我已找到解决方法。我使用了RouteConfiguration组件来设置路由,以及一个GatewayFilter类。在RouteConfiguration的Bean中,我将特定的过滤器设置到了路由路径上。在我的情况下,我使用了一个过滤器来进行身份验证。

**GatewayFilter**

```java
@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();

        // 进行您的业务逻辑,这是一个简单的示例。

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

        return chain.filter(exchange); // 转发到路由
    }

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

RouteConfiguration

@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) // 您可以在这里添加更多的过滤器。
                                .stripPrefix(1))
                        .uri(apiHost))
                .build();
    }
}
英文:

I've found a way to solve it. I've used a RouteConfiguration component to set the routes and a GatewayFilter class. On the RouteConfiguration's Bean I've seted the specific filter to the route path. On my case I've used a filter to make an authentication.

GatewayFilter

@RefreshScope
@Component
public class AuthenticationFilter implements GatewayFilter {

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

    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

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


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

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

    private Mono&lt;Void&gt; onError(ServerWebExchange exchange, String err, HttpStatus httpStatus)  {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        return response.setComplete();
    }

RouteConfiguration

@RefreshScope
@Configuration
public class RouteConfiguration {

    @Value(&quot;${routes.api}&quot;)
    private String apiHost;

    @Autowired
    AuthenticationFilter authenticationFilter;

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

}

答案2

得分: 0

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

@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        // -1是响应写入过滤器,必须在其之前调用
        return -2;
    }
}

拒绝请求并发送错误消息,
您可以查看此方法。使用MonoUtil.buildServerResponse方法。

MonoUtil


<details>
<summary>英文:</summary>

if you want validate every request ,you should impl the Ordered interface and
and return -2;
eg:
   
 @Component

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


refuse a request and send a error message
you can see this .use MonoUtil.buildServerResponse method.

[MonoUtil][1]

  [1]: https://github.com/haochencheng/microservice-integration/blob/master/microservice-integration-gateway/src/main/java/microservice/integration/gateway/filter/GatewayAdminAuthorizationFilter.java

</details>



huangapple
  • 本文由 发表于 2020年7月25日 04:23:58
  • 转载请务必保留本文链接:https://java.coder-hub.com/63080959.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定