[JAVA] Let's implement a function to limit the number of access to the API with SpringBoot + Redis

Introduction

For now, I think there are a lot of cases when using the API interface. However, if you call the API too often, server-side processing may be delayed. It means that something bad will happen. This time, in order to limit the number of times to access the API, I wrote this article after careful examination and thought.

environment

You need to have knowledge of Spring Boot and Redis.

Method

As an implementation policy, it is implemented in the form of annotation mainly using Spring Boot and Redis. You can see the details by looking at the comment out.

1. Create an annotation class

AccessLimit.java


import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author Hyman
 * @date 2019/12/25 11:12
 */
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {

    //Time limit(Seconds)
    int seconds();
    //Maximum number of accesses
    int maxCount();
    //Login status
    boolean needLogin() default true;
}

2. Create an interceptor class

ApiLimitInterceptor.java


import com.alibaba.fastjson.JSON;
import com.example.demo.action.AccessLimit;
import com.example.demo.redis.RedisService;
import com.example.demo.result.CodeMsg;
import com.example.demo.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;

/**
 * @author Hyman
 * @date 2019/12/25 11:22
 */
@Component
public class ApiLimitInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //Find out if the request is a HandlerMethod
        if(handler instanceof HandlerMethod){

            HandlerMethod hm = (HandlerMethod) handler;

            //Get the annotation above the method and see if there is an AccessLimit annotation
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if(accessLimit == null){
         //Do nothing without AccessLimit annotation
                return true;
            }
       
            //Get the time limit
            int seconds = accessLimit.seconds();
            //Get the maximum number of accesses
            int maxCount = accessLimit.maxCount();
            boolean login = accessLimit.needLogin();
            String key = request.getRequestURI();

            //If login is required, do the following:
            if(login){
                //I will omit the authentication part here
                // key+=""+"userInfo";
            }

            //Get access count from redis
            AccessKey ak = AccessKey.withExpire(seconds);
            Integer count = redisService.get(ak,key,Integer.class);

       //Create a branch and perform each process
            if(count == null){
                //When accessing for the first time
                redisService.set(ak,key,1);
            }else if(count < maxCount){
                //Access count + 1
                redisService.incr(ak,key);
            }else{
                //If the maximum number of accesses is exceeded, an error message will be created and returned.
                render(response,"The maximum number of accesses will be exceeded within the specified time."); //
                return false;
            }
        }

        return true;

    }
    
 /**
  *If the maximum number of accesses is exceeded, an error message will be created and returned.
  */
  private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str  = JSON.toJSONString(Result.error(cm));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}

3. Place the interceptor class

Register the interceptor class created in 2 above in Spring Boot.

WebConfig.java


import com.example.demo.ExceptionHander.ApiLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author Hyman
 * @date 2019/11/31 15:58
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private ApiLimitInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}

4. Create a controller

I made a controller and put @AccessLimit above the method. Once that's done, let's test with Postman.

ApiLimitController.java


import com.example.demo.result.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author Hyman
 * @date 2019/12/25 13:21
 */
@Controller
public class ApiLimitController {

    //Time: 5 seconds,Maximum number of accesses:5 ,Login status: Required
    @AccessLimit(seconds=5, maxCount=5, needLogin=true)
    @RequestMapping("/ApiLimitTest")
    @ResponseBody
    public Result<String> ApiLimitTest(){
        return Result.success("Successful request!");
    }
 }

Finally

Thank you for reading to the end. Please do not hesitate to point out any parts that you think are strange. Thank you.

Recommended Posts

Let's implement a function to limit the number of access to the API with SpringBoot + Redis
Try to implement login function with Spring-Boot
How to access Socket directly with the TCP function of Spring Integration
I tried to implement a function equivalent to Felica Lite with HCE-F of Android
Let's write a proxy integrated Lambda function of Amazon API Gateway with Spring Cloud Function
Let's write how to make API with SpringBoot + Docker from 0
[Swift] I tried to implement the function of the vending machine
Find the number of days in a month with Kotlin
Try to imitate the idea of a two-dimensional array with a one-dimensional array
Let's roughly implement the image preview function with Rails + refile + jQuery.
I tried to express the result of before and after of Date class with a number line
I tried to implement the image preview function with Rails / jQuery
A story about hitting the League Of Legends API with JAVA
[Illustration] Finding the sum of coins with a recursive function [Ruby]
A story about using the CoreImage framework to erase stains with Swift and implement a blur erase function
A story that I wanted to write a process equivalent to a while statement with the Stream API of Java8
The story of making a game launcher with automatic loading function [Java]
Let's express the result of analyzing Java bytecode with a class diagram
How to convert an array of Strings to an array of objects with the Stream API
[Rails] Implement the product purchase function with a credit card registered with PAY.JP
I tried to visualize the access of Lambda → Athena with AWS X-Ray
How to implement the email authentication function at the time of user registration
How to determine the number of parallels
[Swift] How to implement the countdown function
How to implement TextInputLayout with validation function
How to get the ID of a user authenticated with Firebase in Swift
Send a notification to slack with the free version of sentry (using lambda)
Make a margin to the left of the TextField
[Swift] How to implement the LINE login function
Overwrite the contents of config with Spring-boot + JUnit5
[swift5] How to implement the Twitter share function
How to implement the breadcrumb function using gretel
[Android] Implement a function to display passwords quickly
Try to implement login function with Spring Boot
[For beginners] How to implement the delete function
Let's make a search function with Rails (ransack)
[Swift] How to implement the fade-in / out function
How to build a Jenkins server with a Docker container on CentOS 7 of VirtualBox and access the Jenkins server from a local PC
Getting Started with Doma-Introduction to the Criteria API
I tried to make a product price comparison tool of Amazon around the world with Java, Amazon Product Advertising API, Currency API (2017/01/29)
I checked the number of taxis with Ruby
I tried to check the operation of http request (Put) with Talented API Tester
How to set the retry limit of sidekiq and notify dead queues with slack
I made a function to register images with API in Spring Framework. Part 1 (API edition)
Count the number of occurrences of a string in Ruby
[Swift] Get the number of steps with CMP edometer
3. Create a database to access from the web module
How to run the SpringBoot app as a service
Access the built-in h2db of spring boot with jdbcTemplate
The story of making a reverse proxy with ProxyServlet
Implement the UICollectionView of iOS14 with the minimum required code.
Limit the number of threads using Java's Executor Service
Why implement with a singleton instead of a static method
I want to add a delete function to the comment function
The story of pushing a Docker container to GitHub Package Registry and Docker Hub with GitHub Actions
How to manage the difference in each environment with yml without increasing the number of RAILS_ENV
Implemented a strong API for "I want to display ~~ on the screen" with simple CQRS
Display a balloon message in BarButtonItem of NavigationBar with a size according to the amount of text.
Let's find out how to receive in Request Body with REST API of Spring Boot
A story about a student who was spared due to the SARS-CoV-2 epidemic and finally made a Twitter App and finally hit the limit of the Twitter API