Search code examples
javaspring-bootspring-cloud-gateway

Spring Cloud Gateway: Set response status code in custom predicate


In the below code snippet, I am trying to match my request against a custom predicate. Upon the predicate being evaluated as false, I would like to send back a custom status code (403 Forbidden in the below snippet) instead of the default 404 that is being sent on predicate failure. Here's what I've tried.

RouteLocator

@Bean
 public RouteLocator customRoutesLocator(RouteLocatorBuilder builder 
  AuthenticationRoutePredicateFactory arpf) {
    return builder.routes()
            .route("id1", r ->r.path("/app1/**")
                .uri("lb://id1")
                .predicate(arpf.apply(new Config()))).build();
}

AuthenticationRoutePredicateFactory

public class AuthenticationRoutePredicateFactory
    extends AbstractRoutePredicateFactory<AuthenticationRoutePredicateFactory.Config> {
public AuthenticationRoutePredicateFactory() {
    super(Config.class);
}

@Override
public Predicate<ServerWebExchange> apply(Config config) {

    return (ServerWebExchange t) -> {
        try {
             Boolean isRequestAuthenticated =  checkAuthenticated();

                return isRequestAuthenticated;
            }
        } catch (HttpClientErrorException e) {
           //This status code does not carried forward and 404 is displayed instead.
            t.getResponse().setStatusCode(HttpStatus.FORBIDDEN); 
            return false;
        }

    };

}

@Validated
public static class Config {

    public Config() {
    }
}

private Boolean checkAuthenticated() {
  // Some sample logic that makes a REST call and returns TRUE/FALSE/HttpClientErrorException
 //Not shown here for simplicity.
  return true;
}

}

When the predicate returns as true, the request is forwarded to the URI. However, on false evaluation 404 is displayed, I require 403 to be displayed (On HttpClientErrorException ). Is this the right way to expect a response with custom status code?. Additionally, I also read on implementing custom webfilters for a given route that can possibly modify the response object before forwarding the request. Is there a way to invoke a filter on predicate failure in that case?


Solution

  • As a newbie to spring cloud gateway, I chose the wrong direction towards approaching this problem.

    Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. Predicates aid the Gateway Handler Mapping in determining if that request matches the route and can only return either a true or false value.

    Once the request matches the route, The handler runs the request through a filter chain that is specific to the request. This is where "pre" or "post" requests can be applied before forwarding a request.

    Therefore In order to send a custom response status code before conditionally forwarding the request, One must write a custom "pre" filter and this can be achieved as below. (Setting 403 status code)

    AuthenticationGatewayFilterFactory

      @Component
     public class AuthenticationGatewayFilterFactory
        extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
    
    
    public AuthenticationGatewayFilterFactory() {
        super(Config.class);
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            try {
                if(isRequestAuthenticated) {
                    return chain.filter(exchange);
                }
                else {
                    
                    exchange.getResponse().setStatusCode(403); // Any status code can be set here.
                    return exchange.getResponse().complete();
                }
                
    
            } catch (HttpClientErrorException e) {
                exchange.getResponse().setStatusCode(403); // Any status code can be set here.
                return exchange.getResponse().complete();
            }
    
        };
    }
    
    public static class Config {
    
    }
    
    private Boolean isRequestAuthenticated(String authToken) {
    
    // Some sample logic that makes a REST call and returns TRUE/FALSE/HttpClientErrorException
     //Not shown here for simplicity.
      return true;
    
    }
    

    RouteLocator

     @Bean
     public RouteLocator customRoutesLocator(RouteLocatorBuilder builder 
      AuthenticationGatewayFilterFactory agff) {
        return builder.routes()
                .route("id1", r ->r.path("/app1/**")
                    .uri("lb://id1")
                    .filter(agff.apply(new Config()))).build();
    }