Changing the Order of the Spring Security WebFilter

I have an API Gateway implemented using Spring Cloud Gateway that uses Spring Security. Spring Security for WebFlux is implemented as a WebFilter right at the beginning of the filter chain. So after successful authentication the request would be forwarded to Spring Cloud Gateway's RoutePredicateHandlerMapping, which would try to deduce the destination based on the URL pattern, and then it would go to a FilteringWebHandler to execute the other filters of Spring Cloud Gateway.

My problem is the following: I have implemented a customized authentication algorithm which uses query string and header variables as credentials for authentication according to the requirements of the project, an this is working without any problem. The problem occurred when we needed to add a small customization for the authentication algorithm that is path independent. When the request reaches the WebFilter of Spring Security, pattern matching is not yet done so I do not know which application does it point to, for example:

app1:

-Path: /app1/**

app2:

-Path: /app2/**

Which means that instead of having authentication -> route mapping -> filtering web handler I should do route mapping -> authentication -> filtering web handler. Not that these three components are not similar, one of them is a filter another is a mapper and the last one is web handler. Now I know how to customize them but the problem is that I do not know how to intercept the Netty server building process in order to change the order of these operations. I need to wait for the building process to end and alter the content of the server before it starts. How can I do that?

Comment From: rwinch

It's not clear to me what you are having problems with. Can you expand your explanation with example request, what happens, and what you want to happen instead? A small demo would be very helpful in this scenario as well.

Comment From: ahmedyarub

Spring Security for WebFlux is implemented as a WebFilter which is executed almost as soon as a request is received. I have implemented custom authentication converter and authentication manager which would extract some variables from the header and URL and use them for authentication. This is working without any problem. Now I needed to add another variable taken from RoutePredicateRouteMapping before authentication is done. What I want exactly is to remove the WebFilter (called WebFilterChainProxy) from its current position and put it between the RoutePredicateRouteMapping and the FilteringWeHandler. Here is how the default process goes: 1- ChannelOperations calls ReactorHttpHandlerAdapter which calls HttpWebHandlerAdapter, ExceptionHandlingWebHandler, and then org.springframework.web.server.handler.FilterWebHandler. This WebHandler would invoke its filters and then call the DispatchHandler. One of those filters is the WebFilterChainProxy that does the authentication for Spring Security. So first step is removing the filter from here. Now the DispatchHandler which is called after the filters would invoke RoutePredicateHandlerMapping, which would analyze the routes and give me the route ID that I need, and then it would call the org.springframework.cloud.gateway.handler.FilteringHandler (this is not the same FilteringHandler above), and that in turn would call the other filters of the Spring Cloud Gateway. What I want here is to invoke the filter after RoutePredicatehandlerMapping and before org.springframework.cloud.gateway.handler.FilteringHandler. What I ended doing was the following: I created and WebHttpHandlerBuilder that would remove WebFilterChainProxy and pass it as a parameter to a customized DispatcherHandler. Now that the filter is removed the request would pass the first layers without requiring authentication. In my customized DispatcherHandler I would invoke the RoutePredicateHandlerMapping and then pass the exchange variable to the WebFilterChainProxy to do the authentication before passing it to the org.springframework.cloud.gateway.handler.FilteringHandler, which worked perfectly! I still think that I'm over engineering it and I hope that there is a way to do it using annotations and configuration beans instead of all these customized classes (WebHttpHandlerBuilder and DispatcherHandler).

Comment From: rwinch

Being unfamiliar with RoutePredicateHandlerMapping, I'm having trouble understand what your goal is. Can you provide a sample?

Comment From: ahmedyarub

RoutePredicateHandlerMapping (https://github.com/spring-cloud/spring-cloud-gateway/blob/master/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java) is an AbstractHandlerMapping (https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/handler/AbstractHandlerMapping.html) whose job is to call the right web handler. In this case it's Spring Cloud Gateway's own FilteringWebHandler (https://github.com/spring-cloud/spring-cloud-gateway/blob/master/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java). This WebHandler calls the filters of the Spring Cloud Gateway, forwards the request to the target endpoint and then return the results to the client. The exact role of the RoutePredicateHandlerMapping matching the URL against the application ID. So after the request passes through this mapper, it would have an additional header that has the ID of the application. I need the ID of the application for authentication, which means I should put the authentication filter AFTER the HandlerMapping and BEFORE the FilterWebHandler. I could solve that easily using a HandlerAdapter, which decouples the HandlerMapping from the WebHandler. I'll post a complete solution as soon as I finish polishing it.

Comment From: rwinch

Thanks. I'm really interested in a sample and detailed explanation of what you want it to do that illustrates your problem. At that point I would be more likely to come up with advice or figure out what needs to be done to make it easier (if necessary).

Comment From: ahmedyarub

So here is how I did it:

Goal: removing the WebFilter of Spring Security from the default HttpHandler, and inserting it between RoutePredicateRouteMapping and the FilteringWebHandler of Spring Cloud Gateway

Why: Because I need to know the Application ID while carrying on my customized authentication process. This Application ID is attached to the request by the RoutePredicateRouteMapping by matching the request's URL to a predefined list.

How did I do it: 1- Removing the WebFilter of Spring Security I created an HttpHandler bean that invokes the default WebHttpHandlerBuilder and then customize the filters. As a bonus, I removed unneeded filters in order to increase the performance of my API Gateway

@Bean
public HttpHandler httpHandler() {
    WebHttpHandlerBuilder webHttpHandlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext);

    MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter = this.applicationContext.getBean(MY_AUTHENTICATED_HANDLER_BEAN_NAME, MyAuthenticationHandlerAdapter.class);

    webHttpHandlerBuilder
            .filters(filters ->
                    myAuthenticationHandlerAdapter.setSecurityFilter(
                            Collections.singletonList(filters.stream().filter(f -> f instanceof WebFilterChainProxy).map(f -> (WebFilterChainProxy) f).findFirst().orElse(null))
                    )
            );

    return webHttpHandlerBuilder.filters(filters -> filters
            .removeIf(f -> f instanceof WebFilterChainProxy || f instanceof WeightCalculatorWebFilter || f instanceof OrderedHiddenHttpMethodFilter))
            .build();
}

2- Wrapping Spring Cloud Gateway's FilteringWebHandler with Spring Web's FilteringWebHandler with the added WebFilter I created my own HandlerAdapter which would match against Spring Cloud Gateway's FilteringWebHandler and wrap it with Spring Web's FilteringWebHandler plus the security filter I extracted in the first step

@Bean
public MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter() {
    return new MyAuthenticationHandlerAdapter();
}

public class MyAuthenticationHandlerAdapter implements HandlerAdapter {
    @Setter
    private List<WebFilter> securityFilter = new ArrayList<>();


    @Override
    public boolean supports(Object handler) {
        return handler instanceof FilteringWebHandler;
    }

    @Override
    public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
        org.springframework.web.server.handler.FilteringWebHandler filteringWebHandler = new org.springframework.web.server.handler.FilteringWebHandler((WebHandler) handler, securityFilter);
        Mono<Void> mono = filteringWebHandler.handle(exchange);
        return mono.then(Mono.empty());
    }
}

This way I could achieve better performance with highly customized HttpHandler pipeline that I suppose to be future-proof

Comment From: rwinch

Thanks for sharing your solution. I'm glad you got it figured out!