我被恐吓了,对方扬言要压测我的网站

大家好我是聪,昨天真是水逆,在技术群里交流问题,竟然被人身攻击了!骂的话太难听具体就不加讨论了,人身攻击我可以接受,我接受不了他竟然说要刷我接口!!!!这下激发我的灵感来写一篇如何抵御黑子的压测攻击,还真得要谢谢他。

image-20240523081706355.png

🔥本次的自动加入黑名单拦截代码已经上传到短链狗,想学习如何生成一个短链可以去我的 Github 上面查看哦,项目地址:https://github.com/lhccong/short-link-dog-backend

思维发散

如果有人要攻击我的网站,我应该从哪些方面开始预防呢,我想到了以下几点,如何还有其他的思路欢迎大家补充:

  1. 从前端开始预防!

    聪 A🧑:确实是一种办法,给前端 ➕ 验证码、短信验证,或者加上谷歌认证(用户说:我谢谢你哈,消防栓)。

    聪 B🧑:再次思考下还是算了,这次不想动我的前端加上如何短信验证还消耗我的💴,本来就是一个练手项目,打住❌。

  2. 人工干预!

    聪 A🧑:哇!人工干预很累的欸,拜托。

    聪 B🧑:那如果是定时人工检查进行干预处理,辅助其他检测手段呢,是不是感觉还行!

  3. 使用网关给他预防!

    聪 A🧑:网关!好像听起来不错。

    聪 B🧑:不行!我项目都没有网关,单单为了黑子增加一个网关,否决❌。

  4. 日志监控!

    聪 A🧑:日志监控好像还不错欸,可以让系统日志的输出到时候统一监控,然后发短信告诉我们。

    聪 B🧑:日志监控确实可以,发短信还是算了,拒绝一切花销哈❌。

  5. 我想到了!后端 AOP 拦截访问限流,通过自动检测将 IP + 用户ID 加入黑名单,让黑子无所遁形。

    聪 A🧑:我觉得可以我们来试试?

    聪 B🧑:还等什么!来试试吧!

功能实现

设置 AOP 注解

1)获取拦截对象的标识,这个标识可以是用户 ID 或者是其他。

2)限制频率。举个例子:如果每秒超过 10 次就直接给他禁止访问 1 分钟或者 5 分钟。

3)加入黑名单。举个例子:当他多次触发禁止访问机制,就证明他还不死心还在刷,直接给他加入黑名单,可以是永久黑名单或者 1 天就又给他放出来。

4)获取后面回调的方法,会用反射来实现接口的调用。

有了以上几点属性,那么注解设置如下:

/**
 * 黑名单拦截器
 *
 * @author cong
 * @date 2024/05/23
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface BlacklistInterceptor {/**
     * 拦截字段的标识符
     *
     * @return {@link String }
     */
    String key() default "default";;/**
     * 频率限制 每秒请求次数
     *
     * @return double
     */
    double rageLimit() default 10;/**
     * 保护限制 命中频率次数后触发保护,默认触发限制就保护进入黑名单
     *
     * @return int
     */
    int protectLimit() default 1;/**
     * 回调方法
     *
     * @return {@link String }
     */
    String fallbackMethod();
}

设置切面具体实现

@Aspect
@Component
@Slf4j
public class RageLimitInterceptor {
    private final Redisson redisson;private RMapCache<String, Long> blacklist;
    // 用来存储用户ID与对应的RateLimiter对象
    private final Cache<String, RRateLimiter> userRateLimiters = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build();public RageLimitInterceptor(Redisson redisson) {
        this.redisson = redisson;
        if (redisson != null) {
            log.info("Redisson object is not null, using Redisson...");
            // 使用 Redisson 对象执行相关操作
            // 个人限频黑名单24h
            blacklist = redisson.getMapCache("blacklist");
            blacklist.expire(24, TimeUnit.HOURS);// 设置过期时间
        } else {
            log.error("Redisson object is null!");
        }
    }
​
​
    @Pointcut("@annotation(com.cong.shortlink.annotation.BlacklistInterceptor)")
    public void aopPoint() {
    }@Around("aopPoint() && @annotation(blacklistInterceptor)")
    public Object doRouter(ProceedingJoinPoint jp, BlacklistInterceptor blacklistInterceptor) throws Throwable {
        String key = blacklistInterceptor.key();// 获取请求路径
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        //获取 IP
        String remoteHost = httpServletRequest.getRemoteHost();
        if (StringUtils.isBlank(key)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "拦截的 key 不能为空");
        }
        // 获取拦截字段
        String keyAttr;
        if (key.equals("default")) {
            keyAttr = "SystemUid" + StpUtil.getLoginId().toString();
        } else {
            keyAttr = getAttrValue(key, jp.getArgs());
        }
​
        log.info("aop attr {}", keyAttr);// 黑名单拦截
        if (blacklistInterceptor.protectLimit() != 0 && null != blacklist.getOrDefault(keyAttr, null) && (blacklist.getOrDefault(keyAttr, 0L) > blacklistInterceptor.protectLimit()
                ||blacklist.getOrDefault(remoteHost, 0L) > blacklistInterceptor.protectLimit())) {
            log.info("有小黑子被我抓住了!给他 24 小时封禁套餐吧:{}", keyAttr);
            return fallbackMethodResult(jp, blacklistInterceptor.fallbackMethod());
        }// 获取限流
        RRateLimiter rateLimiter;
        if (!userRateLimiters.asMap().containsKey(keyAttr)) {
            rateLimiter = redisson.getRateLimiter(keyAttr);
            // 设置RateLimiter的速率,每秒发放10个令牌
            rateLimiter.trySetRate(RateType.OVERALL, blacklistInterceptor.rageLimit(), 1, RateIntervalUnit.SECONDS);
            userRateLimiters.put(keyAttr, rateLimiter);
        } else {
            rateLimiter = userRateLimiters.getIfPresent(keyAttr);
        }// 限流拦截
        if (rateLimiter != null && !rateLimiter.tryAcquire()) {
            if (blacklistInterceptor.protectLimit() != 0) {
                //封标识
                blacklist.put(keyAttr, blacklist.getOrDefault(keyAttr, 0L) + 1L);
                //封 IP
                blacklist.put(remoteHost, blacklist.getOrDefault(remoteHost, 0L) + 1L);
            }
            log.info("你刷这么快干嘛黑子:{}", keyAttr);
            return fallbackMethodResult(jp, blacklistInterceptor.fallbackMethod());
        }// 返回结果
        return jp.proceed();
    }private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        return method.invoke(jp.getThis(), jp.getArgs());
    }/**
     * 实际根据自身业务调整,主要是为了获取通过某个值做拦截
     */
    public String getAttrValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr:{}", attr, e);
            }
        }
        return filedValue;
    }/**
     * 获取对象的特定属性值
     *
     * @param item 对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }/**
     * 根据名称获取方法,该方法同时兼顾继承类获取父类的属性
     *
     * @param item 对象
     * @param name 属性名
     * @return 该属性对应方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }
​
​
}

这段代码主要实现了几个方面:

  • 获取限流对象的唯一标识。如用户 Id 或者其他。
  • 将标识来获取是否触发限流 + 黑名单 如果是这两种的一种,直接触发预先设置的回调(入参要跟原本接口一致喔)。
  • 通过反射来获取回调的属性以及方法名称,触发方法调用。
  • 封禁 标识 、IP 。

代码测试

@BlacklistInterceptor(key = "title", fallbackMethod = "loginErr", rageLimit = 1L, protectLimit = 10)
    @PostMapping("/login")
    public String login(@RequestBody UrlRelateAddRequest urlRelateAddRequest) {
        log.info("模拟登录 title:{}", urlRelateAddRequest.getTitle());
        return "模拟登录:登录成功 " + urlRelateAddRequest.getTitle();
    }public String loginErr(UrlRelateAddRequest urlRelateAddRequest) {
        return "小黑子!你没有权限访问该接口!";
    }
  • key:需要拦截的标识,用来判断请求对象。
  • fallbackMethod:回调的方法名称(这里需要注意的是入参要跟原本接口保持一致)。
  • rageLimit:每秒限制的访问次数。
  • protectLimit:超过每秒访问次数+1,当请求超过 protectLimit 值时,进入黑名单封禁 24 小时。

以下是具体操作截图:

Snipaste_2024-05-23_11-28-41.png

到这里这个黑名单的拦截基本就实现啦,大家还有什么具体的补充点都可以提出来,一起学习一下,经过这次”恐吓风波“,让我知道互联网上的人戾气还是很重的,只要坚持好做自己,管他别人什么看法!!

我是聪ζ希望可以跟大家一起学习,我的 Github:https://github.com/lhccong,如果里面有你感兴趣的项目不妨给我点个星星⭐和关注🔥,未来我还会持续写新的好玩的小项目。

参考文章:https://juejin.cn/post/7309921545638854665

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/649485.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C#多维数组不同读取方式的性能差异

背景 近来在优化一个图像显示程序&#xff0c;图像数据存储于一个3维数组data[x,y,z]中&#xff0c;三维数组为一张张图片数据的叠加而来&#xff0c;其中x为图片的张数&#xff0c;y为图片行&#xff0c;Z为图片的列&#xff0c;也就是说这个三维数组存储的为一系列图片的数据…

记录下所遇到远程桌面连接方法winSCP跟mstsc

之前公司使用过连接远程桌面&#xff0c;今天又遇到要使用远程桌面问题&#xff0c;来记录下。 之前公司使用的是winR 然后回车弹出 后面按照用户名密码就能登陆了 今天后台给了我一张图片准备接着用这个方法&#xff0c;后台就说这个东西要下载winSCP 后台发给我图片 然后去…

mfc140u.dll丢失的解决方法有哪些?怎么全面修复mfc140u.dll文件

mfc140u.dll丢失其实相对来说不太常见到&#xff0c;因为这个文件一般是不丢失的&#xff0c;不过既然有人遇到这种问题&#xff0c;那么小编一定满足各位&#xff0c;给大家详细的唠叨一下mfc140u.dll丢失的各种解决方法&#xff0c;教大家以最快最有效率的方法去解决mfc140u.…

python文件IO基础知识

目录 1.open函数打开文件 2.文件对象读写数据和关闭 3.文本文件和二进制文件的区别 4.编码和解码 读写文本文件时 读写二进制文件时 5.文件指针位置 6.文件缓存区与flush()方法 1.open函数打开文件 使用 open 函数创建一个文件对象&#xff0c;read 方法来读取数据&…

CentOS7 部署单机版 ElasticSearch + Logstash

一、部署ElasticSearch Elasticsearch部署参考下面文章&#xff1a; CentOS7 部署单机版 elasticsearch-CSDN博客文章浏览阅读285次&#xff0c;点赞6次&#xff0c;收藏3次。ElasticSearch&#xff0c;用于检索、聚合分析和大数据存储https://blog.csdn.net/weixin_44295677…

定个小目标之每天刷LeetCode热题(1)

有两种解决方法&#xff1a; 第一种&#xff1a;利用哈希集合不重复的特性&#xff0c;代码展示如下 public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {Set<ListNode> listNode new HashSet<ListNode>();ListNode…

一招搞定!家里灰尘多?教你如何轻松清理,推荐必备神器

在现代生活中&#xff0c;灰尘无处不在&#xff0c;特别是在大城市&#xff0c;空气中的污染物更多&#xff0c;导致家里的灰尘积聚速度加快。保持家居环境的干净和整洁不仅能提升生活质量&#xff0c;还能保护我们的健康。作为一名家电博主将为你提供详细的家里灰尘清理方法&a…

EtherCAT运动控制器在UVW对位平台中的应用

ZMC406硬件介绍 ZMC406是正运动推出的一款多轴高性能EtherCAT总线运动控制器&#xff0c;具有EtherCAT、EtherNET、RS232、CAN和U盘等通讯接口&#xff0c;ZMC系列运动控制器可应用于各种需要脱机或联机运行的场合。 ZMC406支持6轴运动控制&#xff0c;最多可扩展至32轴&#…

Taro React组件开发 —— RuiVerifySlider 行为验证码之滑动拼图

1. 效果预览 2. 使用场景 账号登录,比如验证码发送,防止无限调用发送接口,所以在发送之前,需要行为验证! 3. 插件选择 AJ-Captcha行为验证码文档AJ-Captcha行为验证码代码仓库为什么要选用【AJ-Captcha行为验证码】呢?因为我们管理后台使用的是 pigx ,它在后端采用的是【…

双盲插技术引领行业变革:USB-C便携显示器新篇章

USB TYPE-C接口显示器技术解析&#xff1a;LDR6282芯片与双盲插全功能方案 随着USB TYPE-C接口技术的普及和USB4标准的推出&#xff0c;传统HDMI和DisplayPort接口的地位正逐渐受到挑战。USB TYPE-C接口以其小巧、高速、多功能的特性&#xff0c;正逐步成为显示器和电视机接口…

IDEC和泉触摸屏维修显示屏HG1G-4VT22TF-S

IDEC触摸屏维修故障有&#xff1a;黑屏、花屏、按触摸屏无反应或反应慢、内容错乱、进不了系统界面、无背光、背光暗、有背光无字符、不能通信、按键无反应等。 和泉IDEC触摸屏维修常见型号&#xff1a;HG2A-SS22CF、HG2F-SS22VF 、HG2F-SS52VF、HG3F-FT22TF-B、HG3F-FT22TF-W、…

探索数据结构:单链表的实践和应用

&#x1f511;&#x1f511;博客主页&#xff1a;阿客不是客 &#x1f353;&#x1f353;系列专栏&#xff1a;渐入佳境之数据结构与算法 欢迎来到泊舟小课堂 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ​ 一、前言 前面我们学习了数据结构中的顺序表&…

前端JS必用工具【js-tool-big-box】学习,获取全球重点城市时间

我们去住一些旅馆的时候&#xff0c;或者一些国际性网站&#xff0c;经常可以看见他们的钟表会展示一些国家地区的时间&#xff0c;这个就是很常用的功能。但如果不常接触这个功能的开发网站呢&#xff0c;大家就看自己电脑右下角的时间展示&#xff0c;就是自己当前的具体时间…

2024年【G2电站锅炉司炉】免费试题及G2电站锅炉司炉复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【G2电站锅炉司炉】免费试题及G2电站锅炉司炉复审考试&#xff0c;包含G2电站锅炉司炉免费试题答案和解析及G2电站锅炉司炉复审考试练习。安全生产模拟考试一点通结合国家G2电站锅炉司炉考试最新大纲及G2电站锅…

新手必看!TikTok视频0播放的5大原因及解决方法

很多刚起步的TikTok卖家常会遇到一个烦心事&#xff1a;发布的视频播放量少得可怜&#xff0c;甚至有时是0播放。这确实让人挺沮丧的。到底如何突破TikTok视频总是播放量为0的瓶颈&#xff1f;别担心&#xff0c;今天为大家总结了tiktok视频播放量上不去的5种原因和相应的解决方…

以JVM新特性看Java的进化之路:从Loom到Amber的技术篇章

引言&#xff1a; JVM的最新特性通过在效率、功能和易用性方面的创新&#xff0c;对Java的未来发展产生了深远的影响。以下是几个关键特性如何塑造了Java的未来&#xff1a; 正文&#xff1a; 轻量级并发 - 项目Loom&#xff1a; 项目Loom通过引入虚拟线程&#xff08;也被称为…

使用vue,mybatis,mysql,tomcat,axios实现简单的登录注册功能

目录 第一步环境搭建 后端&#xff1a; 前端&#xff1a; 第二步画流程图 web: service: dao层&#xff1a; 第三步前端代码的实现 这是开始的页面&#xff0c;接下来我们要到router路由下书写#login的路径 路由中的component在我们自己创建的views书写vue文件…

某安全厂商外包安服工程师面试

写在前面&#xff1a;本篇帖子出现的厂商and面试问题皆为up实际面试情况&#xff0c;厂商信息不透露&#xff0c;仅供师傅们参考。 背景 某知名安全厂商外包 岗位&#xff1a;安全服务工程师&#xff08;偏售后&#xff09; 薪资条件&#xff1a;7~9k&#xff08;up所在二线…

Moto和Inter字节序

inter: 低地址按照start_bit位放低字节依次往高字节填充 MotoLsb: 低地址按照start_bit位放高字节&#xff0c;依次往低字节填充MotoMsb&#xff1a;高字节按照start_bit位放低地址&#xff0c;依次往高字节填充

光纤跳纤,这篇文章值得一看

光纤跳线作为光网络布线最基础的元件之一&#xff0c;被广泛应用于光纤链路的搭建中。 如今&#xff0c;光纤制造商根据应用场景的不同推出众多类型的光纤跳线&#xff0c;如 MPO / LC / SC / FC / ST 光纤跳线&#xff0c;单工/双工光纤跳线&#xff0c;单模/多模光纤跳线等&…