Summary
It would be nice if Spring Security supported Rate Limiting. This should provide support for the web, but not be servlet API specific since we will want to support reactive environment, batch and possibly integration environments.
Update
Spring Cloud is offering Rate limiting support https://spring.io/blog/2017/07/06/spring-cloud-finchley-m1-is-available
Comment From: jgoldhammer
It would be nice if I can contribute a first version for this...
My point of view for the requirements: - ratelimit will restrict number of requests for a client, identified by the ip-adress delivered in a http header value * Which header/parameter should be used? Should we check several headers like X-Forwarded-For ... or should the user of the filter should pass in the header name? Maybe supporting both? Fallback is to check several headers if user does not pass in a certain header name? - ratelimits will be applied by a OncePerRequestFilter and must be the first filter in the spring-security-filter chain, so that authentication which can contain heavy weight operations, does not jump in before the ratelimit - ratelimit filter should be configurable * pass in a spring security requestmatcher to only apply the filter to certain urls or operations (optional)- default for all requests * pass in the header name distinguish between clients (optional) * pass in the number of allowed requests of a unique client for a certain time amount (I don´t think we can provide a sensible default for these numbers) - ratelimit filter saves the current number of requests for each client in a caffeine-based cache, based on the ip adress of the client. The cache entry for a certain client will be removed automatically after the configured time amount to free memory because the counter will be reseted. - always include response header X-RateLimit-xxx (see http://stackoverflow.com/questions/16022624/examples-of-http-api-rate-limiting-http-response-headers) - send back correct status code to client "429 Too Many Requests" if ratelimit exceeds (https://tools.ietf.org/html/rfc6585#section-4)
Open: - Should we support that the client has to pass in a unique api key?
What do you think? Any other ideas?
Comment From: jgoldhammer
@rwinch @jgrandja Any feedback for me?
Thanks, Jens
Comment From: rwinch
@jgoldhammer Thanks for taking the time to start to flush this out! A pull request would be most welcome :)
ratelimit will restrict number of requests for a client, identified by the ip-adress delivered in a http header value ** Which header/parameter should be used? Should we check several headers like X-Forwarded-For ... or should the user of the filter should pass in the header name? Maybe supporting both? Fallback is to check several headers if user does not pass in a certain header name?
I think we need to allow the way the client is identified to be somewhat configurable. I can see many APIs having a limit based upon IP address for public requests and limiting the API based upon the principal name for authenticated requests. This is necessary to some degree due to the fact that IP addresses are shared in a NAT. What's more is you may want to have restrictions based upon how much a client (principal name) pays.
ratelimits will be applied by a OncePerRequestFilter and must be the first filter in the spring-security-filter chain, so that authentication which can contain heavy weight operations, does not jump in before the ratelimit
I don't think that we want this to be the first filter. For example, we probably want it to be after ChannelProcessingFilter (makes it so HTTPS is required), HeaderWriterFilter (injects security related headers), etc. We may even need this to be after authentication filters so we can determine the correct principal name for the above requirement. We will likely need to put quite a bit of thought into the order of the Filter. Fortunately, this is an easy change.
ratelimit filter should be configurable * pass in a spring security requestmatcher to only apply the filter to certain urls or operations (optional)- default for all requests * pass in the header name distinguish between clients (optional) ** pass in the number of allowed requests of a unique client for a certain time amount (I don´t think we can provide a sensible default for these numbers)
I can imagine that many users will want to have different rates for different endpoints. For example, they might have a very low limit for something that performs a write, tries to perform authentication, etc. However, they may allow a lot more requests for simple reads (which does not impact the system much).
Likely we will need to have a SecurityMetadataSource that is discovered by a RequestMatcher implementation (for web requests). This is similar to how FilterSecurityInterceptor works.
ratelimit filter saves the current number of requests for each client in a caffeine-based cache,
We will want to provide an interface for how the rate limit is accessed. Likely, one implementation will be Spring's Cache abstraction which would include caffeine as of Spring 4.3.
based on the ip adress of the client. The cache entry for a certain client will be removed automatically after the configured time amount to free memory because the counter will be reseted.
I think that the key will need to be more flexible than the ip address. It might be the principal name too.
always include response header X-RateLimit-xxx (see http://stackoverflow.com/questions/16022624/examples-of-http-api-rate-limiting-http-response-headers)
Likely this will be some sort of implementation of an interface that is invoked. Of course we would provide an implementation that does this out of the box.
send back correct status code to client "429 Too Many Requests" if ratelimit exceeds
Again this would likely be an implementation of an interface that is invoked. We would provide something like this out of the box.
Comment From: jgrandja
@jgoldhammer Thanks for initiating this Jens. I think this will be a valuable feature to provide. Apologize for my delayed response.
ratelimit will restrict number of requests for a client, identified by the ip-adress delivered in a http header value ** Which header/parameter should be used? Should we check several headers like X-Forwarded-For ... or should the user of the filter should pass in the header name? Maybe supporting both? Fallback is to check several headers if user does not pass in a certain header name?
Agreed with @rwinch that relying on the client ip address to track limits won't be accurate in some cases as they are shared at times and therefore does not uniquely identify the client in those instances.
Many of these API Management products provide the ability for a user to subscribe to different plans (for example, gold, silver, bronze) which provide them certain features, not just limited to rate limits. They are provided an API key which is then used when making requests to the various endpoints. The API key would be set in a well-known header which is ultimately configurable in the security configuration. I agree with @rwinch that we may need to authenticate the user first before we can start applying rate limits accurately. The request with the custom-configurable header (the API key) would be intercepted by a filter that ultimately loads the Authentication and related metadata for rate limits per subscribed plan. On the other end though, a header may not be required at all, the user just needs to authenticate and the related rate limit metadata would be loaded into the Authentication.
How can a user/principal be associated (or subscribe) to these plans? The subscribed plans for the user would need to be somehow registered out-of-band beforehand and the UserDetails service would be responsible for loading this metadata into the Authentication. This metadata (plan / rate limits) associated with the Authentication would then be used when checking against the allowed rate limits for a specific request using the preconfigured SecurityMetadataSource as @rwinch suggested.
ratelimit filter should be configurable * pass in a spring security requestmatcher to only apply the filter to certain urls or operations (optional)- default for all requests * pass in the header name distinguish between clients (optional) ** pass in the number of allowed requests of a unique client for a certain time amount (I don´t think we can provide a sensible default for these numbers)
Applying different rate limits for different endpoints per client would be very flexible and definitely a real use case. This again may be achieved through the user's subscribed plans. Or possibly a similar concept to 'scopes' in OAuth. For example:
<intercept-url pattern="/members/**" access="assignedPlan('member')"/>
<intercept-url pattern="/public/**" access="assignedPlan('public')"/>
Whether the concept is a Plan or Scope it's really just a logical name that groups rate limit metadata info. This logical name is then associated to a user who subscribes to it and also used when configuring url's as per above example.
ratelimit filter saves the current number of requests for each client in a caffeine-based cache,
One thing to consider when tracking current hits and saving into the cache is if the environment the app is running in is clustered. In a clustered environment, the tracked rate limits need to be stored in a central store so each node has the current hit rates per client. If the environment is setup for sticky sessions then should be ok. But if requests are load-balanced than a central store would be required.
Comment From: rwinch
I think we should try and separate authorization from rate limiting.
To demonstrate. Consider an application with 100 authorization rules and only two rules for rate limiting...what does the configuration look like if we require the rate limiting be included on the same intercept-url (we have to have 200 configurations)?
The XML configuration already suffers from a similar problem with the requires-channel.
Comment From: jgrandja
Agreed. Obviously not the best example I provided using intercept-url. Separate element/configurer for sure but similar behaviour as the FilterSecurityInterceptor (as you noted) utilizing SecurityMetadataSource (or similar) which would contain the rate limit / plan metadata.
Comment From: jzheaux
Maybe what ought to be done here is what's been done with other large features. Could someone put together a minimal sample that uses a library like Bucket4j and integrates it into Spring Security with a custom filter? It might give everyone something to look at.
Comment From: rwinch
We should also keep in mind that Spring Cloud supports rate limiting now