PingGateway 2024.9

Throttling

To protect applications from being overused by clients, use a throttling filter to limit how many requests can be made in a defined time. The maximum number of requests that are allowed in a defined time is called the throttling rate. The following sections describe how to set up simple, mapped, and scriptable throttling filters:

About throttling

The throttling filter uses the token bucket algorithm, allowing some unevenness or bursts in the request flow. The following image shows how PingGateway manages requests for a throttling rate of 10 requests/10 seconds:

PingGateway allows concurrent requests up to the maximum number defined by the throttling rate, and then allows requests at a constant rate defined by the throttling rate.
  • At 7 seconds, 2 requests have previously passed when there is a burst of 9 requests. PingGateway allows 8 requests, but disregards the 9th because the throttling rate for the 10-second throttling period has been reached.

  • At 8 and 9 seconds, although 10 requests have already passed in the 10-second throttling period, PingGateway allows 1 request each second.

  • At 17 seconds, 4 requests have passed in the previous 10-second throttling period, and PingGateway allows another burst of 6 requests.

When the throttling rate is reached, PingGateway issues an HTTP status code 429 Too Many Requests and a Retry-After header like the following, where the value is the number of seconds to wait before trying the request again:

GET https://ig.example.com:8443/home/throttle-scriptable HTTP/2
. . .

HTTP/2 429 Too Many Requests
Retry-After: 10

Configure simple throttling

This section describes how to configure a simple throttling filter that applies a throttling rate of 6 requests/10 seconds. When an application is protected by this throttling filter, no more than 6 requests, irrespective of their origin, can access the sample application in a 10 second period.

No more than 6 requests in total, irrespective of their origin, can access the application in a 10 second period.
  1. Set up PingGateway for HTTPS, as described in Configure PingGateway for TLS (server-side).

  2. Add the following route to PingGateway:

    • Linux

    • Windows

    $HOME/.openig/config/routes/00-throttle-simple.json
    %appdata%\OpenIG\config\routes\00-throttle-simple.json
    {
      "name": "00-throttle-simple",
      "baseURI": "http://app.example.com:8081",
      "condition": "${find(request.uri.path, '^/home/throttle-simple')}",
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "type": "ThrottlingFilter",
              "name": "ThrottlingFilter-1",
              "config": {
                "requestGroupingPolicy": "",
                "rate": {
                  "numberOfRequests": 6,
                  "duration": "10 s"
                }
              }
            }
          ],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    For information about how to set up the PingGateway route in Studio, refer to Simple throttling filter in Structured Editor.

    Notice the following features of the route:

    • The route matches requests to /home/throttle-simple.

    • The ThrottlingFilter contains a request grouping policy that is blank. This means that all requests are in the same group.

    • The rate defines the number of requests allowed to access the sample application in a given time.

  3. Test the setup:

    1. In a terminal window, use a curl command similar to the route in a loop:

      $ curl -v \
      --cacert /path/to/secrets/ig.example.com-certificate.pem \
      https://ig.example.com:8443/home/throttle-simple/\[01-10\] \
      > /tmp/throttle-simple.txt 2>&1
    2. Search the output file for the result:

      $ grep "< HTTP/2" /tmp/throttle-simple.txt | sort | uniq -c
      
      6 < HTTP/2 200 OK
      4 < HTTP/2 429 Too Many Requests

      Notice that the first six requests returned a success response, and the following four requests returned an HTTP 429 Too Many Requests. This result demonstrates that the throttling filter has allowed only six requests to access the application, and has blocked the other requests.

Configure mapped throttling

This section describes how to configure a mapped throttling policy, where the grouping policy defines criteria to group requests, and the rate policy defines the criteria by which rates are mapped.

The following image illustrates how different throttling rates can be applied to users.

The following image illustrates how each user with a gold status has a throttling rate of 6 requests/10 seconds, and each user with a silver status has 3 requests/10 seconds. The bronze status is not mapped to a throttling rate, and so a user with the bronze status has the default rate.

Different throttling rates are applied to users according to the status.

Before you start, set up and test the example in Validate access tokens with introspection.

  1. In the AM console, select Scripts > OAuth2 Access Token Modification Script and replace the default script as follows:

    import org.forgerock.http.protocol.Request
    import org.forgerock.http.protocol.Response
    
    def attributes = identity.getAttributes(["mail", "employeeNumber"].toSet())
    accessToken.setField("mail", attributes["mail"][0])
    def mail = attributes['mail'][0]
    if (mail.endsWith('@example.com')) {
      status = "gold"
    } else if (mail.endsWith('@other.com')) {
      status = "silver"
    } else {
      status = "bronze"
    }
    accessToken.setField("status", status)

    The AM script adds user profile information to the access token, and defines the content of the users status field according to the email domain.

  2. Add the following route to PingGateway:

    • Linux

    • Windows

    $HOME/.openig/config/routes/00-throttle-mapped.json
    %appdata%\OpenIG\config\routes\00-throttle-mapped.json
    {
      "name": "00-throttle-mapped",
      "baseURI": "http://app.example.com:8081",
      "condition": "${find(request.uri.path, '^/home/throttle-mapped')}",
      "heap": [
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://am.example.com:8088/openam/"
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": [
                  "mail",
                  "employeenumber"
                ],
                "requireHttps": false,
                "realm": "OpenIG",
                "accessTokenResolver": {
                  "name": "token-resolver-1",
                  "type": "TokenIntrospectionAccessTokenResolver",
                  "config": {
                    "amService": "AmService-1",
                    "providerHandler": {
                      "type": "Chain",
                      "config": {
                        "filters": [
                          {
                            "type": "HttpBasicAuthenticationClientFilter",
                            "config": {
                              "username": "ig_agent",
                              "passwordSecretId": "agent.secret.id",
                              "secretsProvider": "SystemAndEnvSecretStore-1"
                            }
                          }
                        ],
                        "handler": "ForgeRockClientHandler"
                      }
                    }
                  }
                }
              }
            },
            {
              "name": "ThrottlingFilter-1",
              "type": "ThrottlingFilter",
              "config": {
                "requestGroupingPolicy": "${contexts.oauth2.accessToken.info.mail}",
                "throttlingRatePolicy": {
                  "name": "MappedPolicy",
                  "type": "MappedThrottlingPolicy",
                  "config": {
                    "throttlingRateMapper": "${contexts.oauth2.accessToken.info.status}",
                    "throttlingRatesMapping": {
                      "gold": {
                        "numberOfRequests": 6,
                        "duration": "10 s"
                      },
                      "silver": {
                        "numberOfRequests": 3,
                        "duration": "10 s"
                      },
                      "bronze": {
                        "numberOfRequests": 1,
                        "duration": "10 s"
                      }
                    },
                    "defaultRate": {
                      "numberOfRequests": 1,
                      "duration": "10 s"
                    }
                  }
                }
              }
            }
          ],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    For information about how to set up the PingGateway route in Studio, refer to Mapped throttling filter in Structured Editor.

    Notice the following features of the route:

    • The route matches requests to /home/throttle-mapped.

    • The OAuth2ResourceServerFilter validates requests with the AccessTokenResolver, and makes it available for downstream components in the oauth2 context.

    • The ThrottlingFilter bases the request grouping policy on the AM user’s email. The throttling rate is applied independently to each email address.

      The throttling rate is mapped to the AM user’s status, which is defined by the email domain, in the AM script.

  3. Test the setup:

    1. In a terminal window, use a curl command similar to this to retrieve an access token:

      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail%20employeenumber" \
      http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
    2. Using the access token, access the route multiple times. The following example accesses the route 10 times and writes the output to a file:

      $ curl -v \
      --cacert /path/to/secrets/ig.example.com-certificate.pem \
      --header "Authorization: Bearer ${mytoken}" \
      https://ig.example.com:8443/home/throttle-mapped/\[01-10\] \
      > /tmp/throttle-mapped.txt 2>&1
    3. Search the output file for the result:

      $ grep "< HTTP/2" /tmp/throttle-mapped.txt | sort | uniq -c
      
      6 < HTTP/2 200
      4 < HTTP/2 429

      Notice that with a gold status, the user can access the route 6 times in 10 seconds.

    4. In AM, change the demo user’s email to demo@other.com, and then run the last two steps again to find how the access is reduced.

Considerations for dynamic throttling

The following image illustrates what can happen when the throttling rate defined by throttlingRateMapping changes frequently or quickly:

The user starts out with a gold status. In a two second period, the users sends five requests, is downgraded to silver, sends four requests, is upgraded back to gold, and then sends three more requests. Each time the user’s status is changed, they are allocated the full throttling rate for the status irrespective of whether they have previously reached his throttling rate for the status.

In the image, the user starts out with a gold status. In a two second period, the users sends five requests, is downgraded to silver, sends four requests, is upgraded back to gold, and then sends three more requests.

After making five requests with a gold status, the user has almost reached their throttling rate. When his status is downgraded to silver, those requests are disregarded and the full throttling rate for silver is applied. The user can now make three more requests even though they have nearly reached their throttling rate with a gold status.

After making three requests with a silver status, the user has reached their throttling rate. When the user makes a fourth request, the request is refused.

The user is now upgraded back to gold and can now make six more requests even though they had reached his throttling rate with a silver status.

When you configure requestGroupingPolicy and throttlingRateMapper, bear in mind what happens when the throttling rate defined by the throttlingRateMapper is changed.

Configure scriptable throttling

This section builds on the example in Configure mapped throttling. It creates a scriptable throttling filter, where the script applies a throttling rate of 6 requests/10 seconds to requests from gold status users. For all other requests, the script returns null, and applies the default rate of 1 request/10 seconds.

Before you start, set up and test the example in Configure mapped throttling.

  1. Add the following route to PingGateway:

    • Linux

    • Windows

    $HOME/.openig/config/routes/00-throttle-scriptable.json
    %appdata%\OpenIG\config\routes\00-throttle-scriptable.json
    {
      "name": "00-throttle-scriptable",
      "baseURI": "http://app.example.com:8081",
      "condition": "${find(request.uri.path, '^/home/throttle-scriptable')}",
      "heap": [
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://am.example.com:8088/openam/"
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": [
                  "mail",
                  "employeenumber"
                ],
                "requireHttps": false,
                "realm": "OpenIG",
                "accessTokenResolver": {
                  "name": "token-resolver-1",
                  "type": "TokenIntrospectionAccessTokenResolver",
                  "config": {
                    "amService": "AmService-1",
                    "providerHandler": {
                      "type": "Chain",
                      "config": {
                        "filters": [
                          {
                            "type": "HttpBasicAuthenticationClientFilter",
                            "config": {
                              "username": "ig_agent",
                              "passwordSecretId": "agent.secret.id",
                              "secretsProvider": "SystemAndEnvSecretStore-1"
                            }
                          }
                        ],
                        "handler": "ForgeRockClientHandler"
                      }
                    }
                  }
                }
              }
            },
            {
              "name": "ThrottlingFilter-1",
              "type": "ThrottlingFilter",
              "config": {
                "requestGroupingPolicy": "${contexts.oauth2.accessToken.info.mail}",
                "throttlingRatePolicy": {
                  "type": "DefaultRateThrottlingPolicy",
                  "config": {
                    "delegateThrottlingRatePolicy": {
                      "name": "ScriptedPolicy",
                      "type": "ScriptableThrottlingPolicy",
                      "config": {
                        "type": "application/x-groovy",
                        "source": [
                          "if (contexts.oauth2.accessToken.info.status == status) {",
                          "  return new ThrottlingRate(rate, duration)",
                          "} else {",
                          "  return null",
                          "}"
                        ],
                        "args": {
                          "status": "gold",
                          "rate": 6,
                          "duration": "10 seconds"
                        }
                      }
                    },
                    "defaultRate": {
                      "numberOfRequests": 1,
                      "duration": "10 s"
                    }
                  }
                }
              }
            }
          ],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    For information about how to set up the PingGateway route in Studio, refer to Scriptable throttling filter in Structured Editor.

    Notice the following features of the route, compared to path]00-throttle-mapped.json:

    • The route matches requests to /home/throttle-scriptable.

    • The DefaultRateThrottlingPolicy delegates the management of throttling to the ScriptableThrottlingPolicy.

    • The script applies a throttling rate to requests from users with gold status. For all other requests, the script returns null and the default rate is applied.

  2. Test the setup:

    1. In a terminal window, use a curl command similar to this to retrieve an access token:

      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail%20employeenumber" \
      http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
    2. Using the access token, access the route multiple times. The following example accesses the route 10 times and writes the output to a file:

      $ curl -v \
      --cacert /path/to/secrets/ig.example.com-certificate.pem \
      --header "Authorization: Bearer ${mytoken}" \
      https://ig.example.com:8443/home/throttle-scriptable/\[01-10\] \
      > /tmp/throttle-script.txt 2>&1
    3. Search the output file for the result:

      $ grep "< HTTP/2" /tmp/throttle-script.txt | sort | uniq -c
      
      6 < HTTP/2 200
      4 < HTTP/2 429

      Notice that with a gold status, the user can access the route 6 times in 10 seconds.

    4. In AM, change the user’s email to demo@other.com, and then run the last two steps again to find how the access is reduced.