Open In App

Spring Security for API Rate Limiting

Last Updated : 22 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

API rate limiting in Spring Security is an essential technique for modern web applications to control client requests to a particular endpoint over a specified time frame. This practice helps protect APIs from abuse, ensures fair resource usage, and prevents Denial of Service (DoS) attacks by controlling traffic to backend services.

In this article, we will explore implementing API rate limiting using Spring Security with a custom filter and an optional Redis-based solution for distributed environments.

Prerequisites:

  • Basic knowledge of the Java and Spring Framework.
  • Familiarity with concepts like filters and authentication.
  • Maven for building dependency management.
  • JDK and IntelliJ IDEA installed in your system.

API Rate Limiting

API rate limiting helps manage traffic by setting the maximum number of requests a user can make to the API within a given time period. For instance, we might restrict users to 100 requests per minute to prevent overuse. When the user exceeds this limit, the server responds with a 429 Too Many Requests status.

Common Approaches to Rate Limiting:

  1. In-Memory Counters: Suitable for simple use cases in a single-instance application.
  2. Distributed Rate Limiting with Redis: Suitable for applications running across multiple instances.
  3. Libraries like Bucket4j: For more advanced rate-limiting scenarios.

Implementing API Rate Limiting with Spring Boot and Spring Security

This example demonstrates how to implement API rate limiting in a Spring Boot application using Spring Security. We'll use an in-memory rate-limiting mechanism in this example.

Step 1: Create a New Spring Boot Project

Create a new Spring Boot project using IntelliJ IDEA and select the following options:

  • Name: rate-limiting-demo
  • Language: Java
  • Type: Maven
  • Packaging: Jar

Click Next, then select the necessary dependencies.

Project Metadata

Step 2: Add the Dependencies

Add the following dependencies into the Spring Boot Project.

  • Spring Web
  • Spring Boot DevTools
  • Lombok
  • Spring Security
  • Spring Data Redis

Click on the Create button.

Add Dependencies

Project Structure

After the project creation done, set the file structure as shown in the below image:

Project Folder Structure

Step 3: Configure Application Properties

In the application.properties file, configure the application and Redis settings (if needed for distributed environments):

# Application settings
spring.application.name=api-rate-limiting
server.port=8080

# Redis configuration (if using Redis for distributed rate limiting)
spring.data.redis.host=localhost
spring.data.redis.port=6379

Step 4: Creating the Rate Limiting Filter

We will create a custom filter that tracks and limits requests based on the client's IP address.

RateLimitingFilter.java:

Java
package com.gfg.apiratelimiting;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class RateLimitingFilter extends OncePerRequestFilter {

    // A thread-safe map to store the number of requests per client IP
    private final Map<String, Integer> requestCounts = new ConcurrentHashMap<>();

    // Define maximum allowed requests per minute
    private static final int MAX_REQUESTS_PER_MINUTE = 5;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Get the client's IP address
        String clientIp = request.getRemoteAddr();

        // Initialize the count if the IP is new, otherwise get the current count
        requestCounts.putIfAbsent(clientIp, 0);
        int requestCount = requestCounts.get(clientIp);

        // If the count exceeds the limit, return a 429 Too Many Requests response
        if (requestCount >= MAX_REQUESTS_PER_MINUTE) {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("Too many requests - please try again later.");
            return;
        }

        // Otherwise, increment the request count and proceed with the request
        requestCounts.put(clientIp, requestCount + 1);
        filterChain.doFilter(request, response);
    }
}
  • This class extends OncePerRequestFilter, meaning the filter is executed once per request.
  • The map requestCounts tracks the number of requests for each client (based on IP).
  • When the request count exceeds the defined MAX_REQUESTS_PER_MINUTE, it returns an HTTP 429 Too Many Requests response.
  • If the limit is not exceeded, the request proceeds as usual.

Step 5: Registering the Rate Limiting Filter in Security Configuration

To apply the rate-limiting filter to your API, we need to register it in the security configuration.

SecurityConfig.java:

Java
package com.gfg.apiratelimiting;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    // Define the security filter chain
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, RateLimitingFilter rateLimitingFilter) throws Exception {
        // Disable CSRF for simplicity and allow all requests
        http.csrf().disable()
                .authorizeHttpRequests()
                .anyRequest().permitAll()
                .and()
                // Register the rate-limiting filter before the UsernamePasswordAuthenticationFilter
                .addFilterBefore(rateLimitingFilter, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}
  • The security configuration disables CSRF (Cross-Site Request Forgery) for simplicity in this example.
  • The custom rate-limiting filter (RateLimitingFilter) is registered before the UsernamePasswordAuthenticationFilter, meaning it will intercept every request before authentication.

Step 6: Create a Controller Class for Testing

We'll create a simple API endpoint to test the rate limiting.

ApiController.java:

Java
package com.gfg.apiratelimiting;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    // A simple GET endpoint for testing rate limiting
    @GetMapping("/api/test")
    public String testApi() {
        return "API is working!";
    }
}
  • This controller defines a simple GET endpoint (/api/test) that returns a success message.
  • We will use this endpoint to test the rate-limiting functionality.

pom.xml File

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gfg</groupId>
    <artifactId>api-rate-limiting</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>api-rate-limiting</name>
    <description>api-rate-limiting</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Step 7: Run the Application

Once the project is complete, run the Spring Boot application on port 8080 using the following command:

mvn spring-boot:run

Output:

Console Output

Step 8: Testing the Rate Limiting

We can use Postman to test the API rate limiting. Here is how the responses should behave:

First 5 requests: The API will respond with a 200 OK status and the message "API is working!".

GET http://localhost:8080/api/test

Postman showing the successful API requests until the rate limit can be reached.

Response:

Postman Response


6th request and beyond (within a minute): The API will respond with a 429 Too Many Requests status and the message "Too many requests - please try again later."

Response:

Postman Response

In this example project, we successfully implemented the simple API rate limiting solution in the Spring Boot using the Spring Security.


Next Article

Similar Reads