Springboot interface current limit to prevent connection point attacks

Article directory

    • summary
    • Overall architecture process
    • Explanation of technical terms
    • technical details
    • summary

Summary

Use aop aspect + redis to implement interface current limit, IP limit, and prevent repeated submission

Overall architecture process

Tips: Use aop aspect + redis to implement interface current limit, IP limit, and prevent repeated submission

Interface file

package com.bzfar.config;


import com.bzfar.enums.LimitType;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter{

    /**
     * Current limiting key
     */
    String key() default "rate_limit:";

    /**
     * Current limiting time, unit seconds
     */
    int time() default 60;

    /**
     * Number of current limits
     */
    int count() default 100;

    /**
     * Current limiting type
     */
    LimitType limitType() default LimitType.DEFAULT;
}
package com.bzfar.config;



import com.aspose.words.net.System.Data.DataException;
import com.bzfar.HeadContext;
import com.bzfar.enums.LimitType;
import com.bzfar.util.RedisUtil;
import com.bzfar.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

@Aspect
@Component
@Slf4j
public class RateLimiterAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;


    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
        String key = rateLimiter.key();
        Long time = new Long(rateLimiter.time());
        int count = rateLimiter.count();

        String combineKey = getCombineKey(rateLimiter, point);
        String keyCode = key + combineKey.hashCode();
        int number = 1;
        if(redisUtil.hasKey(keyCode)){
            number = (Integer)redisUtil.get(keyCode);
             + + number;
        }
        redisUtil.set(keyCode, number, time);
        if(number > count){
            throw new DataException("The access is too frequent, please try again later");
        }
    }

    @After("@annotation(rateLimiter)")
    public void doAfter(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
        String combineKey = getCombineKey(rateLimiter, point);
        redisTemplate.delete(combineKey);
    }

    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            stringBuffer.append(IpUtil.getIp()).append("-");
        }
        if(rateLimiter.limitType() == LimitType.USER){
            stringBuffer.append(HeadContext.getToken()).append("-");
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuffer.toString();
    }
}

type file

package com.bzfar.enums;

import io.swagger.annotations.ApiModel;
import lombok.Getter;

@ApiModel("current limit type")
@Getter
public enum LimitType {

    /** Default policy global traffic restriction */
    DEFAULT,

    /** IP current limit */
    IP,

    /** User ID current limit */
    USER
}
package com.bzfar.submit;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{

}
package com.bzfar.submit;

import com.bzfar.util.AssertUtil;
import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        if (handler instanceof HandlerMethod)
        {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            if (annotation != null)
            {
                if (this.isRepeatSubmit(request))
                {
                    AssertUtil.assertNull("", "Repeated submissions are not allowed, please try again later");
                    return false;
                }
            }
            return true;
        }
        else
        {
            return super.preHandle(request, response, handler);
        }
    }

    /**
     * Verify whether repeated submissions are made by subclasses to implement specific anti-recurring submission rules.
     *
     * @param request
     * @return
     * @throwsException
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request) throws Exception;
}

ip tool class

package com.bzfar.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;

@Slf4j
public class IpUtil {

    // After multiple reverse proxies, there will be multiple separators for IP values.
    private static final String IP_UTILS_FLAG = ",";
    // Unknown IP
    private static final String UNKNOWN = "unknown";
    // local IP
    private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IP1 = "127.0.0.1";

    public static String getIp(){
        // Get the request IP address based on HttpHeaders
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("x-forwarded-for");
            if (ip != null & amp; & amp; ip.length() != 0 & amp; & amp; !UNKNOWN.equalsIgnoreCase(ip)) {
                // After multiple reverse proxies, there will be multiple IP values. The first IP is the real IP.
                if (ip.contains(IP_UTILS_FLAG)) {
                    ip = ip.split(IP_UTILS_FLAG)[0];
                }
            }
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        //Compatible with k8s cluster to obtain ip
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = Objects.requireNonNull(request.getRemoteAddr());
            if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                //Get the IP configured on this machine based on the network card
                InetAddress iNet = null;
                try {
                    iNet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    log.error("getClientIp error: ", e);
                }
                assert iNet != null;
                ip = iNet.getHostAddress();
            }
        }
        return ip;
    }
}

Controller layer annotations

 @PostMapping("addOrUpdateUser")
    @ApiOperation("Add or modify user information and faces")
    @RateLimiter(time = 2, count = 1, limitType = LimitType.IP)
    public HttpResult addOrUpdateUserFace(@Validated @RequestBody AddUserDto dto){
  
        return HttpResult.ok( userService.addUserFace(dto));

    }

Summary

Tips:

@RateLimiter(time = 2, count = 1, limitType = LimitType.IP) annotation will allow access once every 2 seconds based on the fixed ip