Skip to content

QuotaFilter for Spring-Cloud-Gateway #1433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
104 changes: 101 additions & 3 deletions docs/src/main/asciidoc/spring-cloud-gateway.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
= Spring Cloud Gateway

include::_attributes.adoc[]

*{spring-cloud-version}*
Expand Down Expand Up @@ -206,7 +207,6 @@ The following example configures a method route predicate:
.application.yml
====
[source,yaml]
----
spring:
cloud:
gateway:
Expand Down Expand Up @@ -380,7 +380,6 @@ The following `maxTrustedIndex` values yield the following remote addresses:
|3 | 0.0.0.1
|[4, `Integer.MAX_VALUE`] | 0.0.0.1
|===
[[gateway-route-filters]]

The following example shows how to achieve the same configuration with Java:

Expand Down Expand Up @@ -912,6 +911,104 @@ spring:
====


----

=== RequestQuotaFilter GatewayFilter Factory

The RequestQuotaFilter GatewayFilter Factory is uses a `QuotaFilter` implementation to determine if the current request is allowed to proceed.
If it is not, a status of `HTTP 429 - Too Many Requests` (by default) is returned.

This filter takes an optional `keyResolver` parameter and parameters specific to the quota filter (see RequestRateLimiter above).

By default, if the `KeyResolver` does not find a key, requests will be denied.
This behavior can be adjusted with the `spring.cloud.gateway.filter.request-quota-filter.deny-empty-key` (true or false) and `spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code` properties.

NOTE: The RequestQuotaFilter is not configurable via the "shortcut" notation.

==== Redis QuotaFilter

The redis quota filter requires the use of the `spring-boot-starter-data-redis-reactive` Spring Boot starter.

The `redis-quota-filter.limit` is how many requests a user is allowed to do, without any dropped requests.

The `redis-quota-filter.period` is the period how long the limit will exist.
The allowed units are:
`SECONDS, DAYS, HOURS, DAYS or ABS`
The implementation can be found in the `RedisQuotaFilter.QuotaPeriods.class`.
The `ABS` filter period will never expire, so the the limit will be not volatile as the other periods.

.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: request_quota_filter_route
uri: https://example.org
filters:
- name: RequestQuota
args:
redis-quota-filter.limit: 10
redis-quota-filter.period: DAYS

----

This defines a request quota limit of 10 requests per day.

You can use a custom `KeyResolver` where you can specify patterns or request parameter matches where the quota filter will look for.

.Config.java
[source,java]
----
@Bean
KeyResolver customAuth() {
return exchange -> Mono.just(exchange.getRequest().getHeaders().getFirst("X-Authentication"));
}
----

The `KeyResolver` is a simple one that gets the first custom header in the request `X-Authentication`.

The defined `KeyResolver` can be uses with the quota filter like:
.application.yml

[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: request_quota_filter_route
uri: https://example.org
filters:
- name: RequestQuota
args:
redis-quota-filter.limit: 10
redis-quota-filter.period: DAYS
key-resolver: customAuth

----

A quota filter can also be defined as a bean implementing the `QuotaFilter` interface.
In configuration, reference the bean by name using SpEL. `#{@myQuotaFilter}` is a SpEL expression referencing a bean with the name `myQuotaFilter`.

.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://example.org
filters:
- name: RequestQuota
args:
quota-filter: "#{@myQuotaFilter}"
key-resolver: "#{@userKeyResolver}"

----

=== The `RedirectTo` `GatewayFilter` Factory

The `RedirectTo` `GatewayFilter` factory takes two parameters, `status` and `url`.
Expand Down Expand Up @@ -2125,7 +2222,8 @@ The following example configures `/actuator/gateway/routes`:
----
====

This feature is enabled by default. To disable it, set the following property:
This feature is enabled by default.
To disable it, set the following property:

.application.properties
====
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
import org.springframework.cloud.gateway.filter.ForwardPathFilter;
import org.springframework.cloud.gateway.filter.ForwardRoutingFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.KeyResolver;
import org.springframework.cloud.gateway.filter.NettyRoutingFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.PrincipalNameKeyResolver;
import org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
Expand All @@ -74,6 +76,7 @@
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestQuotaGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
Expand All @@ -94,8 +97,7 @@
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver;
import org.springframework.cloud.gateway.filter.quota.QuotaFilter;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.handler.FilteringWebHandler;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
Expand Down Expand Up @@ -483,6 +485,13 @@ public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFac
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}

@Bean
@ConditionalOnBean({ QuotaFilter.class, KeyResolver.class })
public RequestQuotaGatewayFilterFactory requestQuotaGatewayFilterFactory(
QuotaFilter quotaFilter, KeyResolver resolver) {
return new RequestQuotaGatewayFilterFactory(quotaFilter, resolver);
}

@Bean
public RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory() {
return new RewritePathGatewayFilterFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
import org.springframework.cloud.gateway.filter.quota.RedisQuotaFilter;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -55,6 +56,16 @@ public RedisScript redisRequestRateLimiterScript() {
return redisScript;
}

@Bean
@SuppressWarnings("unchecked")
public RedisScript redisRequestQuotaLimiterScript() {
DefaultRedisScript redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(
new ClassPathResource("META-INF/scripts/request_quota_limiter.lua")));
redisScript.setResultType(List.class);
return redisScript;
}

@Bean
@ConditionalOnMissingBean
public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate,
Expand All @@ -63,4 +74,12 @@ public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTempla
return new RedisRateLimiter(redisTemplate, redisScript, configurationService);
}

@Bean
@ConditionalOnMissingBean
public RedisQuotaFilter redisQuotaFilter(ReactiveStringRedisTemplate redisTemplate,
@Qualifier(RedisQuotaFilter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
ConfigurationService configurationService) {
return new RedisQuotaFilter(redisTemplate, redisScript, configurationService);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.springframework.cloud.gateway.filter.ratelimit;
package org.springframework.cloud.gateway.filter;

import reactor.core.publisher.Mono;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.springframework.cloud.gateway.filter.ratelimit;
package org.springframework.cloud.gateway.filter;

import java.security.Principal;

Expand Down
Loading