Intercepting REST Requests With Jakarta REST Request Filters

Photo of Luqman Saeed by Luqman Saeed

Oftentimes in web applications, there is the need to intercept a request from the client to resource methods. Sometimes this interception must take place even before the request is matched to a resource method. For such needs, Jakarta REST provides the jakarta.ws.rs.container.ContainerRequestFilter interface. This interface is a Jakarta REST extension that can be used to intercept requests to resource methods. 

An implementation of this interface can decide if requests should be intercepted before they’re matched to resource methods through the @PreMatching annotation. A pre-matched request filter will be invoked by the container before the request is matched to its intended resource method. 

For this blog post, let us look at two use cases for request filters. One is a situation where for security reasons, certain HTTP methods are not allowed. For example an organisation can have a security rule in its firewall that disallows HTTP PUT methods. In this case, either all methods have to be POST or request filters can be used to workaround the restriction. 

The second situation is for the custom implementation of authentication. Of course you absolutely should NOT hand-roll your own security setup unless you know in detail exactly what you are doing. You are better off using tried and tested security frameworks and services out there. But for this blog post, assuming we need to implement custom security, we can use a pre matching request filter. The following code snippet below shows a ContainerRequestFilter implementation that implements the two scenarios above.

@Provider
@PreMatching
public class CustomRequestFilter implements ContainerRequestFilter {
    @Override
    public void filter(final ContainerRequestContext requestContext) {

        String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        String token = parseAuthToken(authHeader);
        if (!isValid(token)) {
            throw new NotAuthorizedException("Bearer error=\"invalid_token\"");
        }

        String methodOverride = requestContext.getHeaderString("X-Http-Method-Override");
        if (methodOverride != null && !methodOverride.isBlank()) {
            requestContext.setMethod(methodOverride);
        }
    }

    private String parseAuthToken(String httpHeader) {
        if (httpHeader == null || httpHeader.isEmpty()) {
            throw new NotAuthorizedException("Bearer");
        }
        return httpHeader;
    }

    private boolean isValid(String token) {
        return token != null && !token.isBlank();
    }

}

The CustomRequestFilter class is annotated with @Provider and @PreMatching. The first annotation registers this class as aJakarta RESTextension automatically. The second annotation tells the runtime to call this filter to intercept requests before they are matched to their respective methods. In the absence of a @PreMatching annotation, the filter will be invoked to intercept a request _after_ it has been matched to its target method. 

The ContainerRequestFilter interface has a single method - filter - which takes a jakarta.ws.rs.container.ContainerRequestContext parameter. This parameter is a runtime provided implementation that has information about the request being intercepted and methods to make changes to that request. The CustomRequestFilter implementation gets the auth token from the header of the request and then tries to parse it, throwing a jakarta.ws.rs.NotAuthorizedException if it fails. Thus execution will only continue if the authorization header is valid. Of course this is just an example. In a real application, you probably will use helper libraries for parsing the auth token. 

Once authentication and authorisation passes, the implementation then gets X-Http-Method-Override value from the header. This value is the actual method that the client intends to invoke, but had to make a request with a different one due to security restrictions. Thus the implementation gets the actual HTTP method as X-Http-Method-Override, and if it’s not null or a blank string, calls the setMethod of the ContainerRequestContext to set the request method (PUT for example). This way, after this filter exits, the Jakarta REST runtime will match the request to the new method set in the filter.

This is an example of how to use Jakarta REST filters to intercept requests to your resource methods. It is a handy construct that you can use to dynamically alter requests to suit a specific problem in a specific context. 

Found this useful? Try some of our other guides toJakarta EE specifications:

Comments