四时宝库

程序员的知识宝库

面试官问如果我要攻击你的接口,你如何应付

引言:在网络编程中,防止恶意请求是至关重要的。恶意请求可能会导致系统资源的极度浪费,甚至造成系统崩溃或服务拒绝。因此,Java开发者需要采取一系列措施来有效地防止恶意请求的发生。

题目

面试官问如果我要攻击你的接口,你如何应付

推荐解析

思维发散

面试官首先你攻击我的网站是不对的!如果你非要攻击我的网站,我应该从哪些方面开始预防呢,我想到了以下几点,:

从前端开始预防

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

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

人工干预

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

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

使用网关给他预防

思维A:网关!好像听起来不错。

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

日志监控

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

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

后端 AOP 自动检测

思维A:我想到了!后端 AOP 拦截访问限流,通过自动检测将 IP + 用户ID 加入黑名单,让黑子无所遁形。我觉得可以我们来试试?

思维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 小时。

到这里这个黑名单的拦截基本就实现啦,大家还有什么具体的补充点都可以提出来,一起学习一下。

推荐文章和书籍

文章:https://zhuanlan.zhihu.com/p/86293659

书籍:《 Java 核心技术卷 I 》

欢迎交流

当谈到恶意请求时,我们可以探讨以下几个问题:

1)如何识别恶意请求的特征?

2)恶意请求的来源可能有哪些?如何区分恶意请求与正常请求?

3)如何通过日志记录和监控工具来检测和防止恶意请求对系统造成的影响?

这些问题将帮助我们深入了解恶意请求的特征、来源和防范措施,以更好地保护系统和网络安全。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接