Throttle the Rate of Requests to Protected Applications
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 IG manages requests for a throttling rate of 10 requests/10 seconds:
-
At 7 seconds, 2 requests have previously passed when there is a burst of 9 requests. IG 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, IG allows 1 request each second.
-
At 17 seconds, 4 requests have passed in the previous 10-second throttling period, and IG allows another burst of 6 requests.
When the throttling rate is reached, IG 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 http://openig.example.com:8080/home/throttle-scriptable HTTP/1.1 . . . HTTP/1.1 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.
-
Add the following route to IG:
$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 IG route in Studio, see 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.
-
-
Test the setup:
-
With IG and the sample application running, use
curl
, a bash script, or another tool to access the following route in a loop: http://openig.example.com:8080/home/simple-throttle.Accessing the route in a loop runs the request multiple times in quick succession, allowing you to test the throttling rate.
$ curl -v http://openig.example.com:8080/home/throttle-simple/\[01-10\] \ > /tmp/throttle-simple.txt 2>&1
-
Search the output file to see the result:
$ grep "< HTTP/1.1" /tmp/throttle-simple.txt | sort | uniq -c 6 < HTTP/1.1 200 OK 4 < HTTP/1.1 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.
-
Set up AM:
-
Set up AM as described in Validate Access_Tokens Through the Introspection Endpoint.
-
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.
-
-
Set up IG:
-
Set an environment variable for the IG agent password, and then restart IG:
$ export AGENT_SECRET_ID='cGFzc3dvcmQ='
The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.
-
Add the following route to IG:
$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://openam.example.com:8088/openam/", "version": "7.1" } } ], "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 IG route in Studio, see 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.
-
-
-
Test the setup:
-
In a terminal window, use a
curl
command similar to the following to retrieve an access_token:$ mytoken=$(curl -s \ --user "client-application:password" \ --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail%20employeenumber" \ http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
-
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 http://openig.example.com:8080/home/throttle-mapped/\[01-10\] \ --header "Authorization:Bearer ${mytoken}" \ > /tmp/throttle-mapped.txt 2>&1
-
Search the output file to see the result:
$ grep "< HTTP/1.1" /tmp/throttle-mapped.txt | sort | uniq -c 6 < HTTP/1.1 200 4 < HTTP/1.1 429
Notice that with a
gold
status, the user can access the route 6 times in 10 seconds. -
In AM, change the demo user’s email to
demo@other.com
, and then run the last two steps again to see 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:
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.
-
Set up AM as described in Configure Mapped Throttling.
-
Set up IG:
-
Set an environment variable for the IG agent password, and then restart IG:
$ export AGENT_SECRET_ID='cGFzc3dvcmQ='
The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.
-
Add the following route to IG:
$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://openam.example.com:8088/openam/", "version": "7.1" } } ], "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 IG route in Studio, see 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.
-
-
-
Test the setup:
-
In a terminal window, use a
curl
command similar to the following to retrieve an access_token:
-
$ mytoken=$(curl -s \ --user "client-application:password" \ --data "grant_type=password&username={amDemoUn}&password={amDemoPw}&scope=mail%20employeenumber" \ http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
-
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 http://openig.example.com:8080/home/throttle-scriptable/\[01-10\] --header "Authorization:Bearer ${mytoken}" > /tmp/throttle-script.txt 2>&1
-
Search the output file to see the result:
$ grep "< HTTP/1.1" /tmp/throttle-script.txt | sort | uniq -c 6 < HTTP/1.1 200 4 < HTTP/1.1 429
Notice that with a
gold
status, the user can access the route 6 times in 10 seconds. -
In AM, change the user’s email to
demo@other.com
, and then run the last two steps again to see how the access is reduced.