stefanprodan / WebApiThrottle

ASP.NET Web API rate limiter for IIS and Owin hosting
MIT License
1.28k stars 275 forks source link

Question: Using ThrottlingHandler & ThrottlingFilter together #67

Closed Thwaitesy closed 7 years ago

Thwaitesy commented 8 years ago

Hi - Awesome Library!

We have had someone abusing our API, mainly creating tonnes of user accounts (13 thousand over a period of 3 hours or so).

Here is what I am trying to achieve:

  1. A global policy of 50 requests per second, per endpoint, per IP address
  2. A endpoint policy "POST: Users" that limits to 150 new users per hour

Basically - this means that while we are sleeping (say 10 hours) the attacker could only create a maximum of 1500 accounts. It also means that their could only be 150 "Real" user accounts per hour... We can manage that number over time but for now thats more than required.

At this stage this is what I have:

  1. A ThrottlingHandler with the global policy covering off number 1
  2. A ThrottlingFilter - I'm not 100% sure if I need this for the attribute filter but have it anyway.
  3. A [EnableThrottling] attribute on the "POST: Users" endpoint - which covers number 2.

My question: Will the ThrottlingHandler & ThrottlingFilter work independent of each other? Or do they inherit each other's policies? And - does the above look ok?

Cheers, Sam

Thwaitesy commented 8 years ago

Here is my startup.cs code

/* This will be processed first in the pipeline - it is much faster
* 
* + 50 requests per second, per endpoint, per IP address
* 
*/
config.MessageHandlers.Add(new ThrottlingHandler()
{
    Policy = new ThrottlePolicy(perSecond: 50)
    {
        IpThrottling = true,
        IpWhitelist = new List<string> { "127.0.0.1", "192.168.0.0/24" }, //Local host & Local network
        EndpointThrottling = true,
    },
    Logger = new ThrottleLogger(),
    Repository = new CacheRepository(),
});

/* This is handled second - It is turned on by [EnableThrottling] attributes
* 
* + No IP address throttling
* + No endpoint throttling
* 
*/
config.Filters.Add(new ThrottlingFilter()
{
    Policy = new ThrottlePolicy
    {
        IpWhitelist = new List<string> { "127.0.0.1", "192.168.0.0/24" }, //Local host & Local network
    },
    Logger = new ThrottleLogger(),
    Repository = new CacheRepository(),
});

Here is the controller code:

[HttpPost]
[Route("")]
[AllowAnonymous]
[EnableThrottling(PerHour = 150)]
public HttpResponseMessage CreateUser(CreateUser.Request request)
stefanprodan commented 8 years ago

Hi @Thwaitesy, you don't need the handler, the filter should apply on all actions. Removed the handler and your setup is good.

stefanprodan commented 8 years ago

Or you could use only the ThrottlingHandler to block the requests before the http controller dispatcher, it's faster.

You should configure it like this:

config.MessageHandlers.Add(new ThrottlingHandler()
{
    Policy = new ThrottlePolicy(perSecond: 50)
    {
        IpThrottling = true,
        IpWhitelist = new List<string> { "127.0.0.1", "192.168.0.0/24" }, //Local host & Local network
        EndpointThrottling = true,
        EndpointRules = new Dictionary<string, RateLimits>
        { 
            { "users/CreateUser", new RateLimits {  PerHour = 150} }
        }
    },
    Logger = new ThrottleLogger(),
    Repository = new CacheRepository(),
});

Replace the "users/CreateUser" with your controller/action name.

Thwaitesy commented 8 years ago

Ok cool.

Can you add wildcard to the EndpointRules?

E.g. "api/users/{userid}/reports" or "api/users/*/reports"

stefanprodan commented 7 years ago

Wildcards are not supported, the matching is done using contains.

var rules = Policy.EndpointRules.Where(x => identity.Endpoint.Contains(x.Key.ToLowerInvariant())).ToList();
IDisposable commented 7 years ago

using x.Key.ToLowerInvariant() instead of a case-insensitive equality comparison is a performance anti-pattern.

It would be much better to construct the dictionary with a different comparitor

stefanprodan commented 7 years ago

@IDisposable you are right, thanks for pointing this to me.

I've changed it to:

var rules = Policy.EndpointRules.Where(x => identity.Endpoint.IndexOf(x.Key, 0, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();