javaspringspring-cloudspring-cloud-gatewayspring-cloud-security

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


It'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 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'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'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<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...."); // 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


Solution

  • 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<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();
        }
    

    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) // You can add more filters here.
                                    .stripPrefix(1))
                            .uri(apiHost))
                    .build();
        }
    
    }