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

后端 未结 3 725
陌清茗
陌清茗 2020-12-02 19:26

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

@RestController

public class A         


        
相关标签:
3条回答
  • 2020-12-02 19:28

    You don't have that component in 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 algorithm as 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).
    0 讨论(0)
  • 2020-12-02 19:47

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

    There is bucket4j-spring-boot-starter project which uses bucket4j library 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.

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

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

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

    0 讨论(0)
  • 2020-12-02 19:50

    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 LoadingCache class for storing the request counts and client ip addresses. You will also be needing the javax.servlet-api dependency because you will want to use a servlet filter where the request counting takes place. Heres the code:

    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.

    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 (/*).

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

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

    0 讨论(0)
提交回复
热议问题