Spring Boot Starter for Bucket4j
APACHE-2.0 License
:url-repo: https://github.com/MarcGiffing/bucket4j-spring-boot-starter :url: https://github.com/MarcGiffing/bucket4j-spring-boot-starter/tree/master :url-examples: {url}/examples :url-config-cache: {url}/com/giffing/bucket4j/spring/boot/starter/config/cache
image:{url-repo}/actions/workflows/maven.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/maven.yml] image:{url-repo}/actions/workflows/codeql.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/codeql.yml] image:{url-repo}/actions/workflows/pmd.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/pmd.yml]
Project version overview:
== Contents
<> ** <<introduction_filter>> ** <<introduction_method>>
<<project_configuration>> ** <<bucket4j_complete_properties>> *** <<refill_speed>> *** <<rate_limit_strategy>> *** <<skip_execution_predicates>> *** <<cache_key_filter>> *** <>
<> ** <<dynamic_config_updates>> ** <>
<> ** <<migration_guide>> ** <<overview_cache_autoconfiguration>> ** <> ** <<property_configuration_examples>>
[[introduction]] == Spring Boot Starter for Bucket4j
This project is a Spring Boot Starter for Bucket4j, allowing you to set access limits on your API effortlessly. Its key advantage lies in the configuration via properties or yaml files, eliminating the need for manual code authoring.
Here are some example use cases:
The project offers several features, some utilizing Spring's Expression Language for dynamic condition interpretation:
You have two options for rate limit configuration: adding a filter for incoming web requests or applying fine-grained control at the method level.
[[introduction_filter]] === Use Filter for rate limiting
Filters are customizable components designed to intercept incoming web requests, capable of rejecting requests to halt further processing. You can incorporate multiple filters for various URLs or opt to bypass rate limits entirely for authenticated users. When the limit is exceeded, the web request is aborted, and the client receives an HTTP Status 429 Too Many Requests error.
This projects supports the following filters:
[[introduction_method]] === Use Annotations on methods for rate limiting
Utilizing the '@RateLimiting' annotation, AOP intercepts your method. This grants you comprehensive access to method parameters, empowering you to define the rate limit key or conditionally skip rate limiting with ease.
@RateLimiting( // reference to the property file name = "not_an_admin", // the rate limit is per user cacheKey= "#username", // only when the parameter is not admin executeCondition = "#username != 'admin'", // skip when parameter equals admin skipCondition = "#username eq 'admin", // the method name is added to the cache key to prevent conflicts with other methods ratePerMethod = true, // if the limit is exceeded the fallback method is called. If not provided an exception is thrown fallbackMethodName = "myFallbackMethod") public String execute(String username) { log.info("Method with Param {} executed", username); return myParamName; }
// the fallback method must have the same signature
public String myFallbackMethod(String username) {
log.info("Fallback-Method with Param {} executed", username);
return myParamName;
}
The '@RateLimiting' annotation on class level executes the rate limit on all public methods of the class. With '@IgnoreRateLimiting' you can ignore the rate limit at all on class level or for specific method on method level.
@Component @Slf4j @RateLimiting(name = "default") public class TestService {
public void notAnnotatedMethod() {
log.info("Method notAnnotatedMethod");
}
@IgnoreRateLimiting
public void ignoreMethod() {
log.info("Method ignoreMethod");
}
You can find some Configuration examples in the test project: {url-examples}/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method[Examples]
[[project_configuration]] == Project Configuration
[[bucket4j_complete_properties]] === General Bucket4j properties
bucket4j.enabled=true # enable/disable bucket4j support bucket4j.cache-to-use= # If you use multiple caching implementation in your project and you want to choose a specific one you can set the cache here (jcache, hazelcast, ignite, redis)
==== Filter Bucket4j properties
[[refill_speed]] ==== Refill Speed
The refill speed defines the period of the regeneration of consumed tokens. This starter supports two types of token regeneration. The refill speed can be set with the following property:
You can read more about the refill speed in the https://bucket4j.com/8.1.1/toc.html#refill[official documentation].
[[rate_limit_strategy]] ==== Rate Limit Strategy
If multiple rate limits are defined the strategy defines how many of them should be executed.
===== first
The first is the default strategy. This the default strategy which only executes one rate limit configuration. If a rate limit configuration is skipped due to the provided condition. It does not count as an executed rate limit.
===== all
The all strategy executes all rate limit independently.
[[skip_execution_predicates]] ==== Skip and Execution Predicates (experimental)
Skip and Execution Predicates can be used to conditionally skip or execute the rate limiting. Each predicate has a unique name and a self-contained configuration. The following section describes the build in Execution Predicates and how to use them.
===== Path Predicates
The Path Predicate takes a list of path parameters where any of the paths must match. See https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java[PathPattern] for the available configuration options. Segments are not evaluated further.
Matches the paths '/hello', '/world' or '/admin'.
===== Method Predicate
The Method Predicate takes a list of method parameters where any of the methods must match the used HTTP method.
Matches if the HTTP method is 'GET' or 'POST'.
===== Query Predicate
The Query Predicate takes a single parameter to check for the existence of the query parameter.
Matches if the query parameter 'PARAM_1' exists.
===== Header Predicate
The Header Predicate takes to parameters.
. First - The name of the Header Parameter which must match exactly . Second - An optional regular expression where any existing header under the name must match
Matches if the query parameter 'PARAM_1' exists.
===== Custom Predicate
You can also define you own Execution Predicate:
@Component @Slf4j public class MyQueryExecutePredicate extends ExecutePredicate {
private String query;
public String name() {
// The name which can be used on the properties
return "MY_QUERY";
}
public boolean test(HttpServletRequest t) {
// the logic to implement the predicate
boolean result = t.getParameterMap().containsKey(query);
log.debug("my-query-parameter;value:%s;result:%s".formatted(query, result));
return result;
}
public ExecutePredicate<HttpServletRequest> parseSimpleConfig(String simpleConfig) {
// the configuration which is configured behind the equal sign
// MY_QUERY=P_1 -> simpleConfig == "P_1"
//
this.query = simpleConfig;
return this;
}
[[cache_key_filter]] === Cache Key for Filter
To differentiate incoming request (e.g. by IP address) you can provide an expression which is used as a key resolver for the underlying cache.
Depending on the filter method [servlet, webflux, gateway] different SpEL root objects can be used in the expression so that you have a direct access to the method of these request objects:
The configured URL which is used for filtering is added to the cache-key to provide a unique cache-key for multiple URL. You can read more about it https://github.com/MarcGiffing/bucket4j-spring-boot-starter/issues/19[here].
/**
You can define custom beans like the SecurityService which can be used in the SpEl expressions. **/ @Service public class SecurityService {
public String username() { String name = SecurityContextHolder.getContext().getAuthentication().getName(); if(name.equals("anonymousUser")) { return null; } return name; }
[[post-execute-condition]] === Post Execution (Consume) Condition
If you define a post execution condition the available tokens are not consumed on a rate limit configuration execution. It will only estimate the remaining available tokens. Only if there are no tokens left the rate limit is applied by. If the request was proceeded by the application we can check the return value check if the token should be consumed.
Example: You want to limit the rate only for unauthorized users. You can't consume the available token for the incoming request because you don't know if the user will be authenticated afterward. With the post execute condition you can check the HTTP response status code and only consume the token if it has the status Code 401 UNAUTHORIZED.
image::src/main/doc/plantuml/post_execution_condition.png[]
[[features]] == Features
[[dynamic_config_updates]] === Dynamically updating rate limits (experimental) Sometimes it might be useful to modify filter configurations during runtime. In order to support this behaviour a cache-based configuration update system has been added. The following section describes what configurations are required to enable this feature.
==== Properties
===== Filter properties
===== RateLimit properties For each ratelimit a tokens inheritance strategy can be configured. This strategy will determine how to handle existing rate limits when replacing a configuration. If no strategy is configured it will default to 'RESET'.
Further explanation of the strategies can be found at https://bucket4j.com/8.1.1/toc.html#tokensinheritancestrategy-explanation[Bucket4J TokensInheritanceStrategy explanation]
===== Bandwidth properties This property is only mandatory when BOTH of the following statements apply to your configuration.
==== Example project An example on how to dynamically update a filter can be found at: {url-examples}/caffeine[Caffeine example project].
Some important considerations:
[[monitoring]] === Monitoring - Spring Boot Actuator
Spring Boot ships with a great support for collecting metrics. This project automatically provides metric information about the consumed and rejected buckets. You can extend these information with configurable https://micrometer.io/docs/concepts#_tag_naming[custom tags] like the username or the IP-Address which can then be evaluated in a monitoring system like prometheus/grafana.
bucket4j: enabled: true filters:
[[appendix]] == Appendix
[[migration_guide]] === Migration Guide
This section is meant to help you migrate your application to new version of this starter project.
==== Spring Boot Starter Bucket4j 0.12
==== Spring Boot Starter Bucket4j 0.9
==== Spring Boot Starter Bucket4j 0.8
===== Compatibility to Java 8
The version 0.8 tries to be compatible with Java 8 as long as Bucket4j is supporting Java 8. With the release of Bucket4j 8.0.0 Bucket4j decided to migrate to Java 11 but provides dedicated artifacts for Java 8. The project is switching to the dedicated artifacts which supports Java 8. You can read more about it https://github.com/bucket4j/bucket4j#java-compatibility-matrix[here].
===== Rename property expression to cache-key
The property ..rate-limits[0].expression is renamed to ..rate-limits[0].cache-key. An Exception is thrown on startup if the expression property is configured.
To ensure that the property is not filled falsely the property is marked with @Null. This change requires a Bean Validation implementation.
===== JSR 380 - Bean Validation implementation required
To ensure that the Bucket4j property configuration is correct an Validation API implementation is required. You can add the Spring Boot Starter Validation which will automatically configures one.
===== Explicit Configuration of the Refill Speed - API Break
The refill speed of the Buckets can now configured explicitly with the Enum RefillSpeed. You can choose between a greedy or interval refill see the https://bucket4j.com/8.1.1/toc.html#refill[official documentation].
Before 0.8 the refill speed was configured implicitly by setting the fixed-refill-interval property explicit.
These properties are removed and replaced by the following configuration:
You can read more about the refill speed configuration here <<refill_speed>>
[[overview_cache_autoconfiguration]] === Overview Cache Autoconfiguration
The following list contains the Caching implementation which will be autoconfigured by this starter.
[cols="1,1,1"] |=== |Reactive |Name |cache-to-use
|N |{url-config-cache}/jcache/JCacheBucket4jConfiguration.java[JSR 107 -JCache] |jcache
|Yes |{url-config-cache}/ignite/IgniteBucket4jCacheConfiguration.java[Ignite] |jcache-ignite
|no |{url-config-cache}/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java[Hazelcast] |hazelcast-spring
|yes |{url-config-cache}/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java[Hazelcast] |hazelcast-reactive
|Yes |{url-config-cache}/infinispan/InfinispanBucket4jCacheConfiguration.java[Infinispan] |infinispan
|No |{url-config-cache}/redis/jedis/JedisBucket4jConfiguration.java[Redis-Jedis] |redis-jedis
|Yes |{url-config-cache}/redis/lettuce/LettuceBucket4jConfiguration.java[Redis-Lettuce] |redis-lettuce
|Yes |{url-config-cache}/redis/redission/RedissonBucket4jConfiguration.java[Redis-Redisson] |redis-redisson
|===
Instead of determine the Caching Provider by the Bucket4j Spring Boot Starter project you can implement the SynchCacheResolver or the AsynchCacheResolver by yourself.
You can enable the cache auto configuration explicitly by using the cache-to-use property name or setting it to an invalid value to disable all auto configurations.
[[examples]] === Examples
[[property_configuration_examples]] === Property Configuration Examples
Simple configuration to allow a maximum of 5 requests within 10 seconds independently from the user.
bucket4j: enabled: true filters:
Conditional filtering depending of anonymous or logged in user. Because the bucket4j.filters[0].strategy is first you don't have to check in the second rate-limit that the user is logged in. Only the first one is executed.
bucket4j: enabled: true filters:
Configuration of multiple independently filters (servlet|gateway|webflux filters) with specific rate limit configurations.
bucket4j: enabled: true filters: # each config entry creates one servlet filter or other filter