Spring Cloud Gateway - Creating Custom Route Filters

On Spring Cloud Gateway, we can apply some filters for each route. Whenever a request matches a route's path, the filters defined for that route will be executed. There are many things can be done using filters, such as modify request header, redirect to another URL, rewrite path and perform authentication. There are some built-in filters available to use. But, in case you need to implement custom filter, this tutorial shows you how to do so. I'm going to give you example of how to create custom filter by extending AbstractGatewayFilterFactory.

To create a custom filter, we need to create a class that extends AbstractGatewayFilterFactory. The AbstractGatewayFilterFactory itself implements GatewayFilterFactory. If you look at GatewayFilterFactory, there is a method we need to implement GatewayFilter apply(C config). The other methods are already default, so there is only one method to be implemented.

Inside the apply method is where we put the logic. Basically, we read the incoming request, process it with custom logic and modify the request before being passed to the route.

  @Override
  public GatewayFilter apply(Config config) {
      return (exchange, chain) -> {
          
      };
  }

Inside the apply, we should return a lambda function whose first argument is of type ServerWebExchange and the second argument is of type GatewayFilterChain. We can get the HTTP request (ServerHttpRequest) using exchange.getRequest().

To return the modified request use chain.filter whose argument is of type ServerWebExchange as well.

In the example below, the filter is used to add another header if a header value is valid, but reject (return error 401) if the value is invalid or the header is not present.

  package com.woolha.springcloudgateway.filter;
  
  import org.apache.commons.lang.RandomStringUtils;
  import org.springframework.cloud.gateway.filter.GatewayFilter;
  import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.server.reactive.ServerHttpRequest;
  import org.springframework.http.server.reactive.ServerHttpResponse;
  import org.springframework.stereotype.Component;
  import org.springframework.web.server.ServerWebExchange;
  import reactor.core.publisher.Mono;
  
  import java.util.List;
  
  @Component
  public class MyFilter extends AbstractGatewayFilterFactory<MyFilter.Config> {
      public MyFilter() {
          super(Config.class);
      }
  
      private boolean isAuthorizationValid(String authorizationHeader) {
          boolean isValid = true;
  
          // Logic for checking the value
  
          return isValid;
      }
  
      private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus)  {
          ServerHttpResponse response = exchange.getResponse();
          response.setStatusCode(httpStatus);
  
          return response.setComplete();
      }
  
      @Override
      public GatewayFilter apply(Config config) {
          return (exchange, chain) -> {
              ServerHttpRequest request = exchange.getRequest();
  
              if (!request.getHeaders().containsKey("Authorization")) {
                  return this.onError(exchange, "No Authorization header", HttpStatus.UNAUTHORIZED);
              };
  
              String authorizationHeader = request.getHeaders().get("Authorization").get(0);
  
              if (!this.isAuthorizationValid(authorizationHeader)) {
                  return this.onError(exchange, "Invalid Authorization header", HttpStatus.UNAUTHORIZED);
              }
  
              ServerHttpRequest modifiedRequest = exchange.getRequest().mutate().
                      header("secret", RandomStringUtils.random(10)).
                      build();
  
              return chain.filter(exchange.mutate().request(modifiedRequest).build());
          };
      }
  
      public static class Config {
          // Put the configuration properties
      }
  }

Then, use the filter on the route.

  spring:
    application:
      name: springfield
    cloud:
      gateway:
        routes:
        - id: after_route
        uri: lb://QUIMBY/authenticate
        predicates:
        - Method=POST
        - Path=/authenticate
        filters:
        - MyFilter

Every time a request matches the predicate path, the filter will be executed first before the modified request passed to the URI.