美文网首页
利用redis根据ip进行限流

利用redis根据ip进行限流

作者: WAHAHA402 | 来源:发表于2020-03-02 17:48 被阅读0次

    最近做的项目中用到了redis根据ip来限流,防止暴力破解。大致实现思路是,使用ip加密后的字段作为redis的key,过期时间比如设为1分钟,一分钟内该ip每访问一次,value值加一。当value值大于配置文件中配置的访问频率上线值时,返回错误给前端。在filter(也可以在AOP)中加入以下代码,用来做到接口的限流,最终保护你的网站。

    @Slf4j
    @Component
    public class RateLimiterFilter extends OncePerRequestFilter {
        private final static List<String> rateLimiterList = new ArrayList<>();
    
        static {
            //进行限流的接口
            rateLimiterList.add("/api/v1/auth/login");
        }
    
        private final RedisService redisService;
        //从配置文件中读取限流的频率
        @Value("${auth.sms.rate}")
        private int rate;
    
        public RateLimiterFilter(RedisService redisService) {
            this.redisService = redisService;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    
            String url = request.getRequestURI();
    
            if (rateLimiterList.contains(url)) {
    
                String ip = IpUtil.getIp(request);
                String key = IpUtil.getMod5Url(url).substring(0, 8) + "-" + ip;
    
                String cnt = redisService.getValue(key);
    
                if (!Strings.isBlank(cnt) && Integer.parseInt(cnt) > rate) {
                    log.warn("[{}] - [{}] 访问频率上限[%d]次/分钟. key:[{}]", ip, url, key);
    
                    PrintWriter out = response.getWriter();
                    response.setCharacterEncoding("utf-8");
                    response.setContentType("application/json; charset=utf-8");
    //这里返回固定的BaseResponse对象给前端,直接抛异常时发现,在filter中的异常,GlobalExceptionHandler全局异常处理类无法处理
                    out.print(JSONObject.toJSONString(BaseResponse.with(Code.ACCESS_RATE_LIMIT_REACHED.getCode(), "【请求频繁,请稍后再试】"), SerializerFeature.WriteNullStringAsEmpty));
                    out.flush();
                    out.close();
                    return;
                }
    
                redisService.increaseOrExpire(key, 1, TimeUnit.MINUTES);
            }
    //责任链模式,开发时忘了加下面这行,导致所有请求到这个过滤器就断掉了。。。。
            chain.doFilter(request, response);
        }
    
    }
    

    下面时redisService的实现。

    @Component
    @Slf4j
    public class RedisServiceImpl implements RedisService {
    
        private final static String PREFIX = "rework-stats-";
        private final StringRedisTemplate redisTemplate;
    
        public RedisServiceImpl(StringRedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        @Async
        public Boolean delete(String key) {
            log.debug("RedisUtils delete key:{}", genKey(key));
            return redisTemplate.opsForSet().getOperations().delete(genKey(key));
        }
    
        @Override
        public String getValue(String key) {
            return redisTemplate.opsForValue().get(genKey(key));
        }
    
        @Override
        public void increaseOrExpire(String key, long expire, TimeUnit timeUnit) {
    
            String cnt = getValue(key);
    
            key = genKey(key);
    
            if (cnt != null) {
                log.debug("key:{} cnt:{} increment ", key, cnt);
                redisTemplate.opsForValue().increment(key);
            } else {
                redisTemplate.opsForValue().set(key, "1");
                redisTemplate.expire(key, expire, timeUnit);
                log.debug("create rateLimiter expire:{} - {} - {} ", key, expire, timeUnit);
            }
        }
    
        /**
         * 生成统一规则的前缀key
         *
         * @param key key值
         * @return
         */
        private static String genKey(String key) {
            StringBuilder ret = new StringBuilder(PREFIX);
            ret.append(key);
            return ret.toString();
        }
    }
    

    IpUtil的实现:

    public class IpUtil {
    
        public static String getIp(HttpServletRequest request) {
            String ip = request.getHeader("x-forwarded-for");
            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.getRemoteAddr();
            }
            return ip;
        }
    
        public static String getMod5Url(String url) {
            String ret = null;
            try {
                MessageDigest digest = MessageDigest.getInstance("MD5");
                ret = new BigInteger(1, digest.digest(url.getBytes())).toString(16);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return ret;
        }
    }
    

    pom.xml 加入spring-boot-starter-data-redis依赖

    <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-data-redis</artifactId>
           </dependency>
    

    相关文章

      网友评论

          本文标题:利用redis根据ip进行限流

          本文链接:https://www.haomeiwen.com/subject/iizvkhtx.html