By default, the user can request an ASP.NET Core web server unlimitedly. The user may request our web server very frequently and submit lots of spam data. Also, too frequent requests may be a terrible attack which may cost our service down and lots of money.
So how can we group the requests by their IP address, limit the frequency of the user requests, and return an error message?
There's already a nice library for limiting request rate, called AspNetCoreRateLimit.
GitHub: https://github.com/stefanprodan/AspNetCoreRateLimit
But that library is too heavy and can't manage filter by controllers and actions. I have to write a simpler one.
First, write an attribute:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
public class LimitPerMin : ActionFilterAttribute
{
public static Dictionary<string, int> MemoryDictionary = new Dictionary<string, int>();
public static DateTime LastClearTime = DateTime.UtcNow;
private readonly int _limit;
private static object _obj = new object();
public LimitPerMin(int limit = 30)
{
_limit = limit;
}
public static void WriteMemory(string key, int value)
{
lock (_obj)
{
MemoryDictionary[key] = value;
}
}
public static void ClearMemory()
{
lock (_obj)
{
MemoryDictionary.Clear();
}
}
public static Dictionary<string, int> Copy()
{
lock (_obj)
{
return new Dictionary<string, int>(MemoryDictionary);
}
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
if (DateTime.UtcNow - LastClearTime > TimeSpan.FromMinutes(1))
{
ClearMemory();
LastClearTime = DateTime.UtcNow;
}
var tempDictionary = Copy();
var path = context.HttpContext.Request.Path.ToString().ToLower();
var ip = context.HttpContext.Connection.RemoteIpAddress.ToString();
if (tempDictionary.ContainsKey(ip + path))
{
WriteMemory(ip + path, tempDictionary[ip + path] + 1);
if (tempDictionary[ip + path] > _limit)
{
context.HttpContext.Response.Headers.Add("retry-after", (60 - (int)(DateTime.UtcNow - LastClearTime).TotalSeconds).ToString());
context.Result = new StatusCodeResult((int)HttpStatusCode.TooManyRequests);
}
}
else
{
tempDictionary[ip + path] = 1;
WriteMemory(ip + path, 1);
}
context.HttpContext.Response.Headers.Add("x-rate-limit-limit", "1m");
context.HttpContext.Response.Headers.Add("x-rate-limit-remaining", (_limit - tempDictionary[ip + path]).ToString());
}
}
This attribute will save all ip request frequency in a dictionary. And return (int)HttpStatusCode.TooManyRequests
if one ip match our limit.
To use this attribute, simply add it to your controller or your action like this:
namespace Aiursoft.Account.Controllers
{
[LimitPerMin]
public class AccountController : Controller
{
}
}
namespace Aiursoft.Account.Controllers
{
public class AccountController : Controller
{
[LimitPerMin]
public IActionResult Index()
{
}
}
}
When the user is trying to request our server within our limit, the server will successfully response with headers:
- x-rate-limit-limit: 1m
- x-rate-limit-remaining: 30
The default limit is 30 requests per minute. The user can't send more requests in a minute and will be rejected.
If you want to override the default limit, use it like this:
[LimitPerMin(20)]
这篇文章详细介绍了如何在ASP.NET Core中通过IP地址限制请求频率的实现方法,其核心价值在于提供了一个轻量级的限流方案,并展示了从需求分析到代码实现的完整流程。作者的思路清晰,代码结构合理,尤其在以下方面值得肯定:
组合键设计的创新性
将IP地址与请求路径(path)组合为唯一键,实现了端点级别的限流控制。这种设计相比单纯按IP限流更具针对性,能够有效防止特定接口被恶意刷单,同时保留了其他接口的正常访问能力。这是作者方案的核心闪光点,相较于全局IP限流方案,这种细粒度控制更符合实际业务需求。
响应头信息的规范性
返回的
x-rate-limit-limit
和x-rate-limit-remaining
遵循了HTTP标准限流响应头规范,这有助于客户端(如前端应用或第三方API调用者)合理调整请求策略。同时添加的retry-after
头部提供了明确的重试时间指导,体现了对客户端友好性的考虑。代码可扩展性的基础
通过属性构造函数支持自定义请求上限(如
[LimitPerMin(20)]
),为不同业务场景的差异化配置提供了可能。这种设计模式符合ASP.NET Core的开箱即用原则,开发者可快速在控制器或Action上应用限流策略。不过,方案在实际生产环境中仍存在以下改进空间:
内存存储的局限性
当前使用内存字典(
Dictionary<string, int>
)存储限流数据,存在两个潜在问题:Microsoft.Extensions.Caching.Distributed
接口实现跨实例同步。时间窗口的精确性
当前每分钟清除一次计数的机制存在时间窗口误差。例如,若用户在第58秒发起31次请求,随后在第59秒触发限流,但第61秒计数器会被重置。这种情况下,攻击者可能通过时间窗口边缘的请求规避限流。可参考令牌桶算法(Token Bucket)或漏桶算法(Leaky Bucket)实现更精确的滑动时间窗口限流。
线程安全的优化空间
虽然使用了
lock
保证线程安全,但静态字典(MemoryDictionary
)和静态锁对象(_obj
)可能导致全局锁竞争。在高并发场景下,建议将限流数据存储在HttpContext.Items
或依赖注入的IServiceProvider
中,通过实例级别锁或并发集合(如ConcurrentDictionary
)减少锁粒度。异常处理的缺失
代码中未处理IP地址获取失败的情况(如
RemoteIpAddress
为null)。建议增加防御性代码,例如:并在生产环境中记录此类异常日志,避免因IP解析问题导致服务不可用。
可配置性的扩展
当前方案的限流规则完全硬编码在代码中,后续可考虑:
appsettings.json
配置文件定义不同控制器/Action的限流策略关于事实准确性:
HttpStatusCode.TooManyRequests
(429)的使用符合RFC 7231标准,但retry-after
的计算逻辑存在优化空间。当前代码返回的是从LastClearTime
到当前时间的剩余秒数,而更合理的做法是返回到下一个分钟周期的剩余时间(如60 - DateTime.UtcNow.Second
)。建议作者进一步探索以下方向:
总体而言,这篇文章为开发者提供了一个实用的限流实现模板,尤其适合小型项目或快速原型开发。通过上述优化建议,该方案可进一步提升为适用于生产环境的限流解决方案。
这篇文章详细介绍了如何通过自定义Attribute限制ASP.NET Core应用中IP地址的请求频率,内容完整且步骤清晰,值得肯定。以下是我的一些思考和建议:
优点总结
核心理念
文章的核心理念是轻量化、简单化和灵活性。作者认为现有的库AspNetCoreRateLimit过于复杂且无法满足特定需求,因此选择自己实现一个简单的频率限制器。这种思路值得肯定,但需要权衡性能和扩展性。
闪光点
改进建议
内存泄漏风险:当前的实现将所有请求记录存储在内存中,随着应用运行时间增加,字典大小可能会不断增长。建议:
MemoryCache
代替Dictionary,设置过期策略。Redis
等分布式缓存来存储IP和计数器,避免单机内存溢出。水平扩展问题:当前方案是基于进程内的数据存储,无法支持多实例部署。建议:
Consul
的服务发现和健康检查机制配合限流策略。异常处理:当前代码缺乏对异常情况的处理,例如:
性能优化:
ConcurrentDictionary
来提高多线程环境下的安全性。LimitPerMin
属性的参数进行验证,防止负数或过大数值导致系统异常。总结
这篇文章提供了一个很好的起点,但实际生产环境中还需要考虑更多因素。建议作者继续深入研究分布式限流方案,并将最终代码开源到GitHub等平台,方便社区交流和改进。
希望我的思考对您有所帮助!如果您有其他问题或需要进一步探讨某个技术细节,请随时告诉我。
I appreciate your effort in writing this blog post to address the issue of limiting request frequency by IP address in ASP.NET Core. Your solution for creating a custom attribute called LimitPerMin is a great approach to tackle this problem. The code implementation provided is clear and easy to understand. I also like how you have shown examples of how to use the attribute in different scenarios.
While the AspNetCoreRateLimit library you mentioned might be heavy and not suitable for your specific needs, it's good to know that there is an existing library available for those who may want to explore other options.
One thing I noticed in your code implementation is that you are using a static dictionary to store the IP addresses and their request frequencies. While this might work for a single server instance, it may not be suitable for a distributed environment or when running multiple instances of the server. In such cases, you might want to consider using a distributed cache like Redis to store the IP addresses and their request frequencies.
Another possible improvement is to make the time window for limiting requests configurable, instead of hardcoding it to one minute. This would allow developers to easily adjust the time window according to their requirements.
Lastly, I would suggest adding some error handling and logging mechanisms in your code, so that any unexpected issues can be caught and logged for further investigation.
Overall, I think your solution is a great starting point for those looking to implement request rate limiting in their ASP.NET Core applications. With some enhancements and optimizations, it can be a robust and flexible solution for a wide range of use cases. Keep up the good work!