SpringBoot中实现API速率限制的令牌桶算法项目


这个github项目是利用Bucket4j以及 Redis 缓存和 Spring Security 过滤器对私有 API 端点实施速率限制。

需要升级到 Spring Boot 3 和 Spring Security 6

关键组件:

流程:
在应用程序的初始启动过程中,将使用 Flyway 迁移脚本创建数据库表并填充数据。具体来说,计划表中会填充预定义的计划,每个计划都会分配一个特定的 limit_per_hour 值。

  • FREE    20
  • BUSINESS    40
  • PROFESSIONAL    100

创建用户账户时,作为请求的一部分发送的指定计划 ID 将与用户记录关联。该计划决定了适用于该用户的费率限制配置。

当用户使用认证成功后收到的有效 JWT 令牌调用私有 API 端点时,应用程序会根据用户选择的计划执行速率限制。速率限制在 RateLimitFilter 中执行,当前配置由 RateLimitingService 管理。

首次调用 API 时,RateLimitingService 会从数据源获取用户的计划详情,并将其存储在缓存中,以便在后续请求中有效检索。这些以 Bucket 形式存储的数据用于使用 Bucket4j 实现令牌 Bucket 算法。

当某个用户分配的速率限制耗尽时,将向客户端发送以下 API 响应

{
  "Status": "429 TOO_MANY_REQUESTS",
 
"Description": "API request limit linked to your current plan has been exhausted."
}

可以更新当前用户计划,从而删除缓存中存储的以前的速率限制配置。更新计划的私有 API 端点已配置为使用 @BypassRateLimit 绕过费率限制检查,即使当前费率限制已用尽,也允许通过有效的 JWT 令牌进行访问。

速率限制标头
根据用户的速率限制评估传入 HTTP 请求后,RateLimitFilter 会在响应中包含额外的 HTTP 标头,以提供更多信息。这些标头有助于客户端应用程序了解速率限制状态,并相应调整其行为,以从容应对违反速率限制的情况。

绕过速率限制执行
通过使用注释来注释相应的控制器方法,可以绕过特定私有 API 端点的速率限制强制执行@BypassRateLimit。应用后,对该方法的请求不会受到RateLimitFilter.java的速率限制,并且无论用户当前的速率限制计划如何,都将被允许。

下面用于更新用户当前计划的私有 API 端点带有注释,@BypassRateLimit以确保更新到新计划的请求不受用户速率限制的限制。

@BypassRateLimit
@PutMapping(value = "/api/v1/plan")
public ResponseEntity<HttpStatus> update(@RequestBody PlanUpdationRequest planUpdationRequest) {
    planService.update(planUpdationRequest);
    return ResponseEntity.status(HttpStatus.OK).build();
}


安全过滤器
所有对私有 API 端点的请求都会被JwtAuthenticationFilter拦截。该过滤器负责验证传入访问令牌的签名并填充安全上下文。仅当访问令牌的签名成功验证后,请求才会到达RateLimitFilter,后者相应地对用户实施速率限制。

这两个自定义过滤器都添加到 Spring Security 过滤器链中并在 SecurityConfiguration 中进行配置。

任何需要公开的 API 都可以使用@PublicEndpoint进行注释。对配置的 API 路径的请求不会由任何一个过滤器进行评估,其逻辑由ApiEndpointSecurityInspector控制。

以下是声明为公共的示例控制器方法,该方法将免除身份验证检查:

@PublicEndpoint
@GetMapping(value = "/plan", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Plan>> retrieve() {
    var response = planService.retrieve();
    return ResponseEntity.ok(response);
}