In the previous post we have already talked about authentication in Spring Webflux. We implemented a component, that contains a required business logic to process login and signup, and to issue JWT. However, to fullfil this security flow, we also need a component, that we assert a token: extract Authorization header from HTTP request and validate it. In case of successful result, the request continues. In case of invalid token/empty token, the component will interrupt the request’s flow and return 403 Forbidden error.

Traditionally, Spring MVC operated a concept of Webfilters and handler interceptors. They can be still used in annotation-based controllers, written with Webflux. However, if you work with functional-style reactive routes, you need to use HandlerFilterFunction component. From a technical point of view, HandlerFilterFunction represents a function that filters a handler function. We place it to the given router function in order to apply it before the request will go to the handler. In this article, I would like to provide some thoughts about this handy tool.

Do you want to increase your Java collections skills?

This topic is really huge, and a single post is not enough to cover all. That is why I wrote this Practical Guide. Everything you need in the one book. Do you want to become Java ninja?

Using filter functions in Webflux

Before continue, I would recommend you to check my previous publications on two-factor authentication and error handling in Webflux handlers. That is because, I will utilize their codebase in this post. You can find a complete code in this github repository.

In this section we will proceed step by step through all important phases of HandlerFilterFunction development.

Create

First logical step is to implement a new filter function, because it is an interface. A typical implementation should define filter() method, which, according to docs, applies this filter to the given handler function. Take a look on the code snippet below:

@Override
public Mono<ServerResponse> filter(ServerRequest request,
                                   HandlerFunction<ServerResponse> handlerFunction) {
    List<String> headers = request.headers().header("Authorization");
    if (headers.isEmpty()) return ServerResponse.status(403).build();

    String token = headers.get(0);

    return authService.parseToken(token)
            .flatMap(res -> handlerFunction.handle(request))
            .onErrorResume(err -> ServerResponse.status(403).build());
}

Here we will do following things:

  1. Access headers. Note, that Webflux actually returns a list of headers on key
  2. Validate that Authorization header is not empty
  3. Get the first item in the list
  4. Call AuthService.parseToken method, which returns a Mono object with userId (on successful result) or an error (in case of bad or invalid token)

Another thing to note, here is how filter() method moves a request next by the pipeline. The return type is Mono<ServerResponse>. In failed situations, it terminates an execution of a handler, by returning finished server response. In case of success, it applies handler function, which represents the next entity in the chain.

Apply

Once we defined an auth function, we need to apply it to given routes. I created a dummy ProtectedHandler component, that actually does nothing, but we need to have something to secure. Take a look on my implementation below:

@Bean
public RouterFunction<ServerResponse> protectedEndpoint (ProtectedHandler handler){
    return RouterFunctions
            .route(GET("/secured/hello").and(accept(json)), handler::sayHello);
}

Let apply our function to it. For that, you need to:

  1. Inject auth function as an argument to the bean method
  2. Call RouterFunctions.filter method at the end and pass here auth function

Check the code snippet below:

@Bean
public RouterFunction<ServerResponse> protectedEndpoint (ProtectedHandler handler,
                                                         AuthFunction authFunction){
    return RouterFunctions
            .route(GET("/secured/hello").and(accept(json)), handler::sayHello)
            .filter(authFunction::filter);
}

Next phase is to assert, that everything is up and running.

Test

We expect two scenarios here:

  • Passing valid token leads to an access to the secured endpoint
  • Sending request without token results to 403 response

Let start from the end. Call http://localhost:8080/secured/hello without the header. The output is same to the given screenshot below:

Graph 1. Access denied

An another situation is passing a correct token. Remember, that the login flow require two-step verifaction. So, once you obtained a token, put it as Authorization to the request. If it is correct, you should be able to access the secured endpoint:

Graph 2. Successful request

You can note, that using handler filter functions, is actully much easier and more convinient, than traditional webfilters. As this post concentrates specifically on the topic of auth function, other components are omitted. The complete source code can be found in this github repository.

If you have questions, regarding this topic, feel free to ask them in comments below or contact me.

References

  • John Tomcy Hands-On Spring Security 5 for Reactive Applications Packt Publishing, 2018
  • Pieter Van Hees SpringOne platform: Fun with the functional web framework (2018), access here