调查:有多少后端同学是没有做暴力请求限制的 ❔❔❔❔❔❔❔
欢迎大家评论区一起讨论 😄😄😄
- 本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
暴力请求,指在很短时间段内,对接口发起大量请求。
会有什么影响?
如果接口包含了请求数据库,并且未作缓存,会导致某一短时间内数据库压力骤增,很有可能数据库服务器挂掉然后导致整个系统崩溃
如何避免
过滤器:允许代码在请求处理管道中的特定阶段之前或之后运行。
📋 过滤器管道在ASP.NET Core选择要执行的操作后运行
过滤器类型
每种筛选器类型都在筛选器管道中的不同阶段执行,详细内容大家可以去看官方文档哈!
- 授权筛选器
- 首先运行。
- 确定用户是否获得请求授权。
- 如果请求未获授权,可以让管道短路。
- 资源筛选器
- 授权后运行。
- OnResourceExecuting 在筛选器管道的其余阶段之前运行代码。 例如,
OnResourceExecuting在模型绑定之前运行代码。 - OnResourceExecuted 在管道的其余阶段完成之后运行代码。
- 操作筛选器
- 在调用接口方法之前和之后立即运行。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
- 异常筛选器
- 结果筛选器
下图为各种类型过滤器在管道中的交互方式
使用过滤器
我们要实现限制请求,即在接口请求之前(Action Filters)就需要验证用户是否出现了大量重复请求,我们自定义一个限制请求的过滤器。
// 自定义RequestLimit特性继承ActionFilter 重写OnActionExecuted()方法 即在请求之前实现自己逻辑
public class RequestLimitAttribute : ActionFilterAttribute
{
private readonly int requstLimit = 10; // 定义时间周期内可重复执行次数
public RequestLimitAttribute(int RequstLimit)
{
requstLimit = RequstLimit;
}
public RequestLimitAttribute()
{
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// 类型转换ControllerActionDescriptor 可以获取到Action 和 Controller相关信息
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
// 定义客户端请求头唯一性,可以是设备Id 也可以是IP根据业务场景来
var only = context.HttpContext.Request.Headers["XXXX"];
if (string.IsNullOrWhiteSpace(only))
{
// 来源不明直接拒绝访问
throw new Exception("访问来源不详,禁止访问!");
}
// 定义缓存key 为 唯一标识=> 控制器 => 方法
string key = $"{only}:{descriptor.ControllerName}:{descriptor.ActionName}";
// 获取缓存请求次数Value
var cacheVal = WebApiRedisHelper.Get(key);
int count = 1;
if (cacheVal != null)
{
// 获取缓存key剩余时间
var time = WebApiRedisHelper.Ttl(key);
count = int.Parse(cacheVal) + 1;
if (count > requstLimit)
throw new Exception("访问频次太高了,请休息一下再来访问");
// 更新缓存Key的次数,将缓存剩余时间写入
WebApiRedisHelper.Set(key, count, Convert.ToInt32(time));
return;
}
// 第一次缓存 次数 1,周期为60s
WebApiRedisHelper.Set(key, count, 60);
}
}
- 使用:局部使用(直接使用 [RequestLimit] 特性即可应用于控制器所有方法)
[ApiController]
[RequestLimit]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
......
......
......
}
- 使用:全局使用(StartUp类中ConfigureServices添加相关代码即可全局使用,默认覆盖所有Controller)
注意:全局注册不能出现两个构造函数,可以直接使用一个赋默认值即可。
services.AddControllers(x => x.Filters.Add<RequestLimitAttribute>());
如何忽略过滤器?
即在某些特定场景下某个API不需要执行指定过滤器,我们如何处理?
// 新增AllowRequestLimit同样继承ActionFilterAttribute 重写之后不做任何处理即可,在需要忽略的方法使用特性标注即可
public class AllowRequestLimitAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
}
public override void OnActionExecuting(ActionExecutingContext context)
{
}
}
- 程序如何知道你想要忽略呢
// 验证控制器 或者 Action标注了AllowRequest
public bool SkipValidationFilter(ActionExecutedContext context)
{
//判断是否跳过授权过滤器 两种维度,一种是控制器级别,一种是Action级别,如果忽略我们需要验证是否添加了跳过授权头
// 1.验证Controller
//判断Controller是否跳过验证
if (context.Controller.GetType().GetCustomAttributes(typeof(AllowRequestLimitAttribute), false).Any())
{
return true;
}
//判断Action是否跳过验证
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
if (controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.Any(a => a.GetType().Equals(typeof(AllowRequestLimitAttribute))))
{
return true;
}
}
return false;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var skip = SkipValidationFilter(context);
if (!skip)
{
// 走过滤器验证逻辑
}
}
验证
- 验证Redis存储是否正确(key,val,time)都正常
- 验证暴力访问