Having multiple request IPs in Spring WebTestClient

1k views Asked by At

I'm introducing Bucket4J in my Spring Web application. A basic test setup can be found here:

Bucket4J offers to rate limit on IP basis - so every IP gets its own pool of tokens. This can be done by adding expression: "getRemoteAddress()" to the config:

bucket4j:
enabled: true
filters:
  - metrics:
    types:
      - consumed-counter
      - rejected-counter
  - cache-name: buckets
    filter-method: webflux
    url: .*
    filter-order: 1
    rate-limits:
      - bandwidths:
          - capacity: 1
            time: 10
            unit: seconds
        expression: "getRemoteAddress()"

I'm having a hard time figuring out how to programmatically test if filter by IP is working.

The test for a single IP looks like this:

@ActiveProfiles("test")
@SpringBootTest
class RateLimitTest(
    @Autowired val context: ApplicationContext
) {

@Test
fun `FAILS with status code 429 if rate limit is exceeded`() {
    // arrange
    val client = WebTestClient
        .bindToApplicationContext(context)
        .configureClient()
        .build()

    // ac
    client.get()
        .uri("/api/someendpoint")
        .exchange()
        .expectStatus().isOk
        .expectHeader().valueEquals("X-Rate-Limit-Remaining", "0")

    client.get()
        .uri("/api/someendpoint")
        .exchange()
        .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS)
        .expectBody().jsonPath("error", "Too many requests!")

    // assert

}

What should a test look like that verifies behaviour for multiple IPs / IP rate limiting?
FAILS with status code 429 if rate limit * for IP * is exceeded

1

There are 1 answers

1
nykon On BEST ANSWER

Actually my approach was wrong in general. The application in question is behind a load balancer so querying getRemoteAddress() will only give me the IP of the load balancer.

I had to use the header X-FORWARDED-FOR and this made it super easy to create a test for it

bucket4j:
  enabled: true
  filters:
    - metrics:
        types:
          - consumed-counter
          - rejected-counter
    - cache-name: buckets
      filter-method: webflux
      url: ^(/api/someendpoint).*
      filter-order: 1
      rate-limits:
        - bandwidths:
            - capacity: 1
              time: 10
              unit: seconds
          expression: "getHeaders()['X-FORWARDED-FOR']"

and the test for this is

   @Test
    fun `SUCCESSFULLY rate by X-FORWARDED-FOR header`() {
    // arrange
   val endpoint = "/api/someendpoint"

    // act
    WebTestClient
        .bindToApplicationContext(context)
        .configureClient()
        .defaultHeader("X-FORWARDED-FOR", "1.1.1.1")
        .build()
        .get()
        .uri(endpoint)
        .exchange()
        .expectStatus().isOk
        .expectHeader().valueEquals("X-Rate-Limit-Remaining", "0")

    WebTestClient
        .bindToApplicationContext(context)
        .configureClient()
        .defaultHeader("X-FORWARDED-FOR", "1.1.1.2")
        .build()
        .get()
        .uri(endpoint)
        .exchange()
        .expectStatus().isOk
        .expectHeader().valueEquals("X-Rate-Limit-Remaining", "0")

    WebTestClient
        .bindToApplicationContext(context)
        .configureClient()
        .defaultHeader("X-FORWARDED-FOR", "1.1.1.1")
        .build()
        .get()
        .uri(endpoint)
        .exchange()
        .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS)
        .expectBody().jsonPath("error", "Too many requests!")

    // assert

}