JAX-RS user-based rate-limiting with JSR-375

Last Updated:  June 16, 2022 | Published: August 26, 2018

Recently I had the requirement for rate-limiting access to specific JAX-RS endpoints and to keep track of the user's current amount of API calls. To solve this problem I asked Adam Bien (@AdamBien) in his monthly Airhacks Q&A about this requirement and he gave me a hint for a possible solution while using the ContainerRequestFilter interface for filtering access to JAX-RS resources.

In this blog post, I'll show you how to implement a simple user-based rate-limiting for a JAX-RS endpoint. I'll deploy the application to Payara and make use of Payara's in-memory H2 database to store the available users and their current/max API budget.  To secure the resource I'll use the JSR-375 (Java EE Security API) and define an IdentitiyStore based on a database.

Please note: The provided example contains a database to store information about the rate-limiting. This might not be fast or scalable enough for your use case. Consider using a cache or an in-memory approach instead.

JAX-RS application setup

Let's start with the JAX-RS resource we want to secure. In our example, I am defining one resource which is available under /resources/stocks and returns a hard-coded stock price for Google's Alphabet share:

Next, we need @RolesAllowed to only allow users with the role USER to access this endpoint. Unkown users will therefor get a 401 Unauthorized HTTP Status and won't have access to this endpoint.

To define the IdentityStore and the authentication mechanism I make use of some annotations from the new Java EE Security API and define a configuration class:

The first annotation is responsible for setting up the IdentityStore based on the provided configurations. I am using Payara's default data source and define the required SQL statements for validating an incoming user and for retrieving its roles. In addition, I am selecting the hash algorithm which should be used for hashing the passwords (Pbkdf2PasswordHash is the default algorithm).

Next, the second annotation is for configuring the authentication mechanism. For simplification, I decided to use a @BasicAuthenticationMechanismDefinition  but there is also a form-based or a custom mechanism available.

Furthermore, we use the third annotation to define the available roles for the application's context.

Prepare the database to store rate-limiting information

For storing the user information in the database I modeled two JPA entities: User and UserRoles (the table design is not a best practice as I denormalized the database structure, so don't use this at home):

The User table contains two columns for storing the current amount of API calls and the maximum amount of API calls within a minute.

For a quick setup of pre-defined users and roles I am inserting some users on the application's startup:

The Pbkdf2PasswordHash is available through CDI and can be used anywhere in your code to create a new hash or verify an incoming hash.

For re-setting the API budget for every user, I am using an EJB timer to schedule this task every minute:

Rate-limiting the access with a JAX-RS filter

Now comes the interesting part for the implementation of the ContainerRequestFilter interface:

The class provides an implementation for the filter() method and is recognized from JAX-RS through the @Provider annotation. To update all current users I am injecting the EntityManager to this filter. As this is not an EJB we need the  @Transactional annotation here to explicitly wrap this code in a transaction. The code will first retrieve the username from the Principal. In addition, we'll then check if the current budget of the user allows a new API call. If there is not enough budget the filter returns with an HTTP Status code 429 – Too Many Requests.

Accessing the endpoint with the credentials for rieckpil as a base64 encoded string less than eleven times will result in a valid result:

After the tenth API call, the user will get the following result and has to wait one minute:

You can try this example on your local machine as I provided a Docker image in the GitHub repository.

If you are new to JAX-RS, start with this introduction. For further JAX-RS examples, have a look at the following overview page.

Happy rate-limiting with JAX-RS,

Phil

>