java 如何在 Spring Boot 中为每个用户设置速率限制?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44042412/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 07:54:05  来源:igfitidea点击:

How to set rate limit for each user in Spring Boot?

javarestspring-mvcspring-bootcontroller

提问by Ricky

I am developing a Spring Boot Rest API which handles a lots of incoming request calls. My Controller is something like below:

我正在开发一个 Spring Boot Rest API,它处理大量传入的请求调用。我的控制器如下所示:

@RestController

public class ApiController {
    List<ApiObject>  apiDataList;   

    @RequestMapping(value="/data",produces={MediaType.APPLICATION_JSON_VALUE},method=RequestMethod.GET)
    public ResponseEntity<List<ApiObject>> getData(){                                       
        List<ApiObject> apiDataList=getApiData();
        return new ResponseEntity<List<ApiObject>>(apiDataList,HttpStatus.OK);
    }
    @ResponseBody 
    @Async  
    public List<ApiObject>  getApiData(){
        List<ApiObject>  apiDataList3=new List<ApiObject> ();
        //do the processing
        return apiDataList3;
    }
}

So now I wanted to set a ratelimit for each user. Say every user can only request 5 request per minute or something like that. How to set the rate limit for each user to make only 5 api calls per minute and if a user requests more than that I can send a 429 response back? Do we need thier IP Address?

所以现在我想为每个用户设置一个速率限制。假设每个用户每分钟只能请求 5 个请求或类似的请求。如何将每个用户的速率限制设置为每分钟仅进行 5 次 api 调用,如果用户请求的次数超过此次数,我可以发回 429 响应?我们需要他们的 IP 地址吗?

Any help is appreciated.

任何帮助表示赞赏。

采纳答案by Daniel Cerecedo

You don't have that component in Spring.

您在 Spring 中没有该组件。

  • You can build it as part of your solution. Create a filter and register it in your spring context. The filter should check incoming call and count the incoming requests per user during a time window. I would use the token bucket algorithmas it is the most flexible.
  • You can build some component that is independent of your current solution. Create an API Gateway that does the job. You could extend Zuul gateway and, again, use the token bucket algorithm.
  • You can use an already built-in component, like Mulesoft ESB that can act as API gateway and supports rate limiting and throttling. Never used it myself.
  • And finally, you can use an API Manager that has rate limiting and throttling and much more. Checkout MuleSoft, WSO2, 3Scale,Kong, etc... (most will have a cost, some are open source and have a community edition).
  • 您可以将其构建为解决方案的一部分。创建一个过滤器并将其注册到您的 spring 上下文中。过滤器应检查传入呼叫并计算时间窗口内每个用户的传入请求。我会使用令牌桶算法,因为它是最灵活的。
  • 您可以构建一些独立于当前解决方案的组件。创建一个 API 网关来完成这项工作。您可以扩展 Zuul 网关,并再次使用令牌桶算法。
  • 您可以使用已经内置的组件,例如 Mulesoft ESB,它可以充当 API 网关并支持速率限制和节流。自己没用过。
  • 最后,您可以使用具有速率限制和节流等功能的 API 管理器。结帐 MuleSoft、WSO2、3Scale、Kong 等...(大多数会产生费用,有些是开源的并且有社区版)。

回答by Lukasz R.

Spring does not have rate-limiting out of the box.

Spring 没有开箱即用的速率限制。

There is bucket4j-spring-boot-starterproject which uses bucket4jlibrary with token-bucket algorithm to rate-limit access to the REST api. You can configure it via application properties file. There is an option to limit the access based on IP address or username.

有一个bucket4j-spring-boot-starter项目,它使用bucket4j库和令牌桶算法来限制对REST api 的访问。您可以通过应用程序属性文件对其进行配置。有一个选项可以根据 IP 地址或用户名限制访问

As an example simple setup which allows a maximum of 5 requests within 10 seconds independently from the user:

作为一个简单的设置示例,它允许在 10 秒内独立于用户的最多 5 个请求:

bucket4j:
  enabled: true
  filters:
  - cache-name: buckets
    url: .*
    rate-limits:
    - bandwidths:
      - capacity: 5
    time: 10
    unit: seconds

If you are using Netflix Zuulyou could use Spring Cloud Zuul RateLimitwhich uses different storage options: Consul, Redis, Spring Data and Bucket4j.

如果您使用Netflix Zuul,您可以使用Spring Cloud Zuul RateLimit,它使用不同的存储选项:Consul、Redis、Spring Data 和 Bucket4j。

回答by Maurice

Here is a solution for those who seek to throttle the requests per second for each user (ip address). This solution requires Google's Guava library. You are going to use the LoadingCacheclass for storing the request counts and client ip addresses. You will also be needing the javax.servlet-apidependency because you will want to use a servlet filterwhere the request counting takes place. Heres the code:

这是针对那些寻求限制每个用户(IP 地址)每秒请求数的人的解决方案。此解决方案需要 Google 的Guava library. 您将使用LoadingCache该类来存储请求计数和客户端 IP 地址。您还将需要javax.servlet-api依赖项,因为您将希望使用发生servlet filter请求计数的地方。代码如下:

import javax.servlet.Filter;


@Component
public class requestThrottleFilter implements Filter {

    private int MAX_REQUESTS_PER_SECOND = 5; //or whatever you want it to be

    private LoadingCache<String, Integer> requestCountsPerIpAddress;

    public requestThrottleFilter(){
        super();
        requestCountsPerIpAddress = CacheBuilder.newBuilder().
                expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, Integer>() {
            public Integer load(String key) {
                return 0;
            }
        });
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        String clientIpAddress = getClientIP((HttpServletRequest) servletRequest);
        if(isMaximumRequestsPerSecondExceeded(clientIpAddress)){
          httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
          httpServletResponse.getWriter().write("Too many requests");
          return;
         }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean isMaximumRequestsPerSecondExceeded(String clientIpAddress){
        int requests = 0;
        try {
            requests = requestCountsPerIpAddress.get(clientIpAddress);
            if(requests > MAX_REQUESTS_PER_SECOND){
                requestCountsPerIpAddress.put(clientIpAddress, requests);
                return true;
             }
        } catch (ExecutionException e) {
            requests = 0;
        }
        requests++;
        requestCountsPerIpAddress.put(clientIpAddress, requests);
        return false;
    }

    public String getClientIP(HttpServletRequest request) {
        String xfHeader = request.getHeader("X-Forwarded-For");
        if (xfHeader == null){
            return request.getRemoteAddr();
        }
        return xfHeader.split(",")[0]; // voor als ie achter een proxy zit
    }

    @Override
    public void destroy() {

    }
}

So what this basically does is it stores all request making ip addresses in a LoadingCache. This is like a special map in which each entry has an expiration time. In the constructor the expiration time is set to 1 second. That means that on the first request an ip address plus its request count is only stored in the LoadingCache for one second. It is automatically removed from the map on expiration. If during that second more requests are coming from the ip address then the isMaximumRequestsPerSecondExceeded(String clientIpAddress)will add those requests to the total request count but before that checks whether the maximum request amount per second has already been exceeded. If thats the case it returns true and the filter returns an error response with statuscode 429 which stands for Too many requests.

所以这基本上做的是它将所有请求制作 ip 地址存储在一个LoadingCache. 这就像一个特殊的映射,其中每个条目都有一个过期时间。在构造函数中,过期时间设置为 1 秒。这意味着在第一次请求时,一个 IP 地址加上它的请求计数只会在 LoadingCache 中存储一秒钟。它会在到期时自动从地图中删除。如果在那一秒内有更多请求来自 ip 地址,isMaximumRequestsPerSecondExceeded(String clientIpAddress)则将这些请求添加到总请求计数中,但在此之前检查是否已超过每秒最大请求量。如果是这种情况,它返回 true 并且过滤器返回一个错误响应,状态码为 429,代表请求过多。

This way only a set amount of requests can be made per user per second.

通过这种方式,每个用户每秒只能发出一定数量的请求。

EDIT: Make sure you let Spring do a component scan on the package where you have your Filter saved or else the Filter won't work. Also, because it is annotated with @Component the filter will work for all endpoints by default (/*).

编辑:确保让 Spring 对保存过滤器的包进行组件扫描,否则过滤器将无法工作。此外,因为它是用@Component 注释的,所以过滤器默认适用于所有端点(/*)。

If spring detected your filter you should see something like this in the log during startup.

如果 spring 检测到您的过滤器,您应该在启动期间在日志中看到类似的内容。

o.s.b.w.servlet.FilterRegistrationBean : Mapping filter:'requestThrottleFilter' to: [/*]

o.s.b.w.servlet.FilterRegistrationBean : Mapping filter:'requestThrottleFilter' to: [/*]