laitimes

SpringBoot implements API interface request brush resistance, and the code is very elegant!

author:Program Ape Freak
SpringBoot implements API interface request brush resistance, and the code is very elegant!

1 Why do interface brush proof?

If your server application is attacked by some people, writing scripts to constantly brush a certain interface of the server, so that the pressure gathering on the server side is very large, and may even bring disaster to the server, and if it involves payment-related services, it will be even more lossy. Therefore, we can do an interface brush-proof function, if the server receives a certain user-side request number in a certain period of time exceeds the number we set, it can directly not allow it to access, which is to achieve interface brush-proof.

2 API interface brush proof principle

The principle is actually very simple, record the number of requests from the client on the server side, and if the number of client requests exceeds the set number of requests, it will be directly not allowed.

3 SpringBoot implements API interface brush resistance

To make our code very elegant, we use custom annotations to implement it here, while using Redis to record the number of client requests.

3.1 Custom API brush proof

Below is a note on customizing the interface to prevent clients from flashing.

The code example is as follows:

package com.test.merservice.controller.manage;

import java.lang.annotation.*;

/**
 * API防刷自定义注解(设置默认每秒只能请求1次)
 */
@Documented
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestAntiBrushing {
    int second() default 1;//时间范围内(秒)
    int maxCount() default 1;//最大请求书
}           

3.2 Custom API brush blocking request blocker

The main function logic of the custom interceptor to complete API tamper prevention is to intercept requests before requests and verify whether the number of requests exceeds the set limit.

Examples of diamagnetic are as follows:

package com.test.merservice.controller.manage;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

/**
 * 客户端API防刷拦截器
 */
@Slf4j
@Component
public class RequestAntiBrushingIntercept extends HandlerInterceptorAdapter {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /**
         * instanceof关键字是判断是否某个类的子类
         */
        if(handler.getClass().isAssignableFrom(HandlerMethod.class)){//isAssignableFrom()判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口(isAssignableFrom()方法是判断是否为某个类的父类)
            //HandlerMethod 封装方法定义相关的信息,如类,方法,参数等
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            // 获取方法中是否包含注解
            RequestAntiBrushing methodAnnotation = method.getAnnotation(RequestAntiBrushing.class);
            //获取 类中是否包含注解,也就是controller 是否有注解
            RequestAntiBrushing classAnnotation = method.getDeclaringClass().getAnnotation(RequestAntiBrushing.class);
            // 如果 方法上有注解就优先选择方法上的参数,否则类上的参数
            RequestAntiBrushing requestLimit = methodAnnotation != null?methodAnnotation:classAnnotation;
            if(requestLimit != null){
                if(isLimit(request,requestLimit)){
                    resonseOut(response,Result.error(ApiResultEnum.REQUST_LIMIT));
                    return false;
                }
            }
        }
        return super.preHandle(request, response, handler);
    }

    /**
     * 校验请求是否超过限定值
     */
    public boolean isLimit(HttpServletRequest request,RequestAntiBrushing requestLimit){
        // 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。
        String limitKey = request.getServletPath()+request.getSession().getId();
        // 从缓存中获取,当前这个请求访问了几次
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if(redisCount == null){
            //初始 次数
            redisTemplate.opsForValue().set(limitKey,1,requestLimit.second(), TimeUnit.SECONDS);
        }else{
            if(redisCount.intValue() >= requestLimit.maxCount()){
                return true;
            }
            // 次数自增
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

    /**
     * 把结果返回给客户端
     * @param response
     * @param result
     * @throws IOException
     */
    private void resonseOut(HttpServletResponse response, Result result) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null ;
        String json = JSONObject.toJSON(result).toString();
        out = response.getWriter();
        out.append(json);
    }
}           

3.3 Custom interceptor registration

package com.test.merservice.controller.manage;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Slf4j
@Component
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private RequestAntiBrushingIntercept requestAntiBrushingIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("注册拦截");
        registry.addInterceptor(requestAntiBrushingIntercept);
    }
}           

3.4 API brush proof annotation use

By default, the annotation is the maximum request per second by the client, and the annotation is set to a maximum of 3 requests per second.

package com.sllt.merservice.controller.manage;

import io.seata.core.model.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user/index")
public class IndexController {

    @PostMapping("/index")
    @RequestAntiBrushing(maxCount = 3,second = 1)
    public Result test(){
        //调用service层业务省略
        return Result.ok();
    }
}