How to use the HTTP Filter endpoint to intercept secured API requests

Using the HTTP Filter endpoint to intercept an API request to an ad-hoc API exposed in Martini. The article will guide you through setting up the HTTP Filter endpoint including the required configurations and services.

How does it work?

The HTTP filter endpoint in Martini allows developers to invoke services upon the receipt of a request. Using this feature, we can implement an interceptor service that is triggered before the actual request is sent to the ad-hoc REST API endpoint that is also exposed in Martini.

This guide is geared towards advanced users so this article assumes that you have prior knowledge about Martini, as well as a good understanding of technologies used for this demo.

Implementation

The resulting folder structure in your package should look something like this. In this walkthrough, you can ignore the services_sql, and config folder.

Start by creating the code directories you see in the screenshot above excluding config, and services_sql.

Creating the Interceptor Service

The interceptor service will handle the checking if the request was sent securely or not. In this demo, HMAC will be used as the security scheme for the authenticated request. In the groovy folder, create the  Groovy class for the HmacUtil, and copy the code below:

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.InvalidKeyException
import java.security.Signature

class HmacUtil {

public static String hmac_sha1( String key, String data ) {
try {
Mac mac = Mac.getInstance( 'HmacSHA1' )
SecretKeySpec secretKeySpec = new SecretKeySpec( key.getBytes(), 'SHA1' )
mac.init( secretKeySpec )
byte[] digest = mac.doFinal( data.getBytes() )

String encodedData = digest.encodeBase64().toString()
return encodedData
} catch ( InvalidKeyException e ) {
throw new RuntimeException( 'Invalid key exception while converting to HMac SHA1' )
}
}

public static boolean hmac_verify( String key, String sig, String data ) {
String hmacSig = hmac_sha1( key, data )

println "=========Generated sig: ${hmacSig}\tReceived Sig: ${sig}========="

return sig.equals( hmacSig )
}
}


The code above will be our HMAC signature generator and checker for verifying the HMAC signature that will be sent along the request that we will intercept using the HTTP Filter endpoint

Then we will need some helper classes that will handle the requests and responses the interceptor service will intercept. In the same folder, create two new Groovy classes using the code snippets below

CachedBodyHttpServletInputStream

import javax.servlet.ReadListener
import javax.servlet.ServletInputStream

public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream

public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}

@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}

@Override
public boolean isFinished() {
return cachedBodyInputStream.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener( ReadListener listener ) {
throw new RuntimeException( 'Not implemented' )
}
}


CachedBodyHttpServletRequest

import demo016.groovy.CachedBodyServletInputStream

import javax.servlet.ServletInputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletRequestWrapper

import org.springframework.util.StreamUtils

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody

public CachedBodyHttpServletRequest( HttpServletRequest request ) throws IOException {
super( request )
InputStream requestInputStream = request.getInputStream()
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream)
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream( this.cachedBody )
}

@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( this.cachedBody )
return new BufferedReader( new InputStreamReader( byteArrayInputStream ) )
}
}


The two new Groovy classes that were introduced will be used to extract the request body from the request so we can sign it later.

Once you have that sorted, create a new Martini Service with the name of your choice under the services folder with the following parameters:

Input

  • $__request - object with class name javax.servlet.http.HttpServletRequest

  • $__response - object with class name javax.servlet.http.HttpServletResponse

  • $__chain - object with class name  javax.servlet.FilterChain

Output

  • $__discontinueChain - boolean

  • $__newResponse - object with class name javax.servlet.http.HttpServletResponse

  • $__newRequest - object with class name javax.servlet.http.HttpServletRequest

The resulting output should look like this

After specifying what the inputs and outputs the interceptor service will have, it’s time to work on the rest of the service logic. Here’s what it is going to roughly look like at the end of this section

The first thing you need to do is to add a try-catch block. In the try block, add a new Map step, and add set expressions to the following properties that was previously declared

$__newResponse

$__newRequest


payloadString

This property needs to be declared in the Map step, once declared, provide the set expression below

The resulting Input/Output tree in the map step should look similar below:

The next step is to add the Groovy method/function that was defined in the HmacUtil class that was created in the previous steps. Refer to the image below

In the service mapper view, directly map the payloadString to data, and use set expression to extract the request headers that will be intercepted by this service you are working on right now. Finally, declare a new property for the output of this function/method call and map the output from hmac_verify function to it. After doing so, your Mapper UI should look similar below:

The logger step is optional. Going forward, add a new Fork Step using the property declared for identifying if the HMAC signature provided in the request is valid or not. After adding the Fork Step, add two Blocks that will hold the logic when isValid is true or false. Refer to the image below.

The false Block

The first block added will be labeled as false. Inside the false Block step, add a new Script Step, and a Map step. In the Script Step, use code snippet below:

$__newResponse.setStatus( 401 )
$__newResponse.setContentType( 'application/json' )
$__newResponse.getWriter().write( '{\n\t"message": "invalid HMAC signature"\n}' )
$__newResponse.getWriter().flush()

The resulting set expression should be similar to this:

The true block

Repeat the same steps you have taken in the false Block step. With the changes on the Script Step only:

The Catch block

In the catch block, just add an error logger that will display what went wrong during the execution if the service suddenly throws an exception.

Configuring the HTTP Filter Endpoint

Now that we have sorted the Martini Service that will be triggered upon intercepting a request made to the API that will be added in the filter, It’s time to create the HTTP Filter endpoint. Create the HTTP Filter endpoint under the Endpoints node of your package.

The resulting configuration will look like this

  • In the Service section, select the interceptor service that you created in the previous step.

  • Under the HTTP Filter Configuration, select the Filter Order to Pre Authentication. This will be the point at which the configured service will be executed when a request is matched. In this case, we want to trigger the interceptor service before the actual request is passed to the target service that will be triggered by the request.

  • In the later section, you will create the service and will be exposing it as an ad-hoc request with request method POST, so select the POST item in the Request Methods section.

Creating a Service and Exposing it via Ad-Hoc REST

In the services folder, create a new service. This service will be used to expose an Ad-Hoc REST API with request method POST with a path that matches the path that was defined in the HTTP Filter configuration. To make things simple, the ad-hoc REST API will only return what value was provided in the x-event header that will be included in the request. 

Create the service with the following parameters:

Input

  • payload - a data model with several properties (refer to screenshot at the end of this list)

  • $request - object with class name javax.servlet.http.HttpServletRequest

Output

  • response - a data model with a string property called message

The resulting service inputs and output should be similar below


Once the inputs and output has been defined, finish the rest of the service body. Refer to the image below:

After defining the service logic. In the properties table located at the right side of the UI, look for the REST tab. This tab will appear if you click on an empty space within the service body.


Configure your service with the same configuration, and the ad-hoc REST API is now done.

HTTP Filter in Action

Now that you have all the stuff you need, it’s time to see it in action. Open the HTTP Client and start sending a request to the ad-hoc REST API you just created. Use the following values for the following headers:

  • x-api-key - GwlLUAQyDjZ3LpIeVv9LSefdq3arT6ws

  • x-hmac-signature - Kwe1173k9WtPUoI/x+qOki4dvOY=

  • x-event - either DEVICE_BREAKDOWN or JOB_CREATED

Samples

A sample request body

The header configuration

Sample request+response

A request with invalid HMAC signature

Further Reading

more →