IP 限流
本页介绍 Ape‑Volo‑Admin 中 IP 限流的使用与实现,包括核心接口、注入与注册、配置说明、白名单/黑名单、分布式部署与最佳实践。
IP 限流实现一览
- 基于 AspNetCoreRateLimit 中间件实现,支持按 IP + Endpoint 的限流策略。
- 支持全局规则与端点级规则,支持白名单(IP/客户端/端点)。
- 支持内存存储与 Redis 分布式存储,在多实例下共享计数器。
- 支持自定义超限响应(状态码/内容类型/消息体)。
IP 限流注册
位置:Ape.Volo.Infrastructure/Extensions/IpRateLimitExtensions.cs
csharp
/// <summary>
/// IP限流扩展配置
/// </summary>
public static class IpRateLimitExtensions
{
public static void AddIpStrategyRateLimitService(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.Configure<IpRateLimitOptions>(App.Configuration.GetSection("IpRateLimiting"));
services.Configure<IpRateLimitPolicies>(App.Configuration.GetSection("IpRateLimitPolicies"));
if (App.GetOptions<SystemOptions>().UseRedisCache)
{
var redisOptions = App.GetOptions<RedisOptions>();
services.AddStackExchangeRedisCache(option =>
{
if (!string.IsNullOrWhiteSpace(redisOptions.Password))
{
option.Configuration =
$"{redisOptions.Host}:{redisOptions.Port},password={redisOptions.Password},defaultDatabase={redisOptions.Index + 2}";
}
else
{
option.Configuration = redisOptions.Host + ":" + redisOptions.Port;
}
option.InstanceName = "rateLimit:";
});
services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
}
else
{
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
}
services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}
}说明:
- 当
SystemOptions.UseRedisCache = true时,限流计数与策略存储使用IDistributedCache(Redis),适合多实例部署;否则使用内存存储(单实例)。 AsyncKeyLockProcessingStrategy用于同一 Key 的并发互斥,避免竞态条件导致计数不准。
中间件
位置:Ape.Volo.Infrastructure/Middleware/IpLimitMiddleware.cs
csharp
/// <summary>
/// IP限流策略中间件
/// </summary>
public static class IpLimitMiddleware
{
private static readonly ILogger Logger = SerilogManager.GetLogger(typeof(IpLimitMiddleware));
public static void UseIpLimitMiddleware(this IApplicationBuilder app)
{
if (app.IsNull())
throw new ArgumentNullException(nameof(app));
try
{
if (App.GetOptions<MiddlewareOptions>().IpLimit.Enabled)
{
app.UseIpRateLimiting();
}
}
catch (Exception e)
{
Logger.Error($"Error occured limiting ip rate.\n{e.Message}");
throw;
}
}
}中间件启用顺序建议:
- 放在
UseRouting()之后、UseEndpoints()之前,尽量靠前拦截以降低系统开销;与认证/授权的相对顺序可按业务选择。
🔧 IP 限流配置
配置文件设置
在 IpRateLimit.json 中配置限流相关参数(示例为有效 JSON):
json
{
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": ["127.0.0.1", "::1/10", "192.168.0.0/24"],
"QuotaExceededResponse": {
"Content": "{\"status\":429,\"message\":\"访问过于频繁,请稍后重试!\"}",
"ContentType": "application/json",
"StatusCode": 429
},
"EndpointWhitelist": ["get:/api/license", "*:/api/status"],
"ClientWhitelist": ["dev-id-1", "dev-id-2"],
"GeneralRules": [
{ "Endpoint": "get:/auth/captcha*", "Period": "5s", "Limit": 3 },
{ "Endpoint": "*/auth/*", "Period": "10s", "Limit": 5 },
{ "Endpoint": "*/api/*", "Period": "10s", "Limit": 8 }
]
},
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "127.0.0.1",
"Rules": [{ "Endpoint": "*", "Period": "1s", "Limit": 5 }]
}
]
}
}主要参数说明
| 参数 | 说明 | 示例/默认值 |
|---|---|---|
| EnableEndpointRateLimiting | 是否启用端点级限流(按 HTTP 方法 + 路径匹配) | true |
| StackBlockedRequests | 达到限制后是否继续累计阻塞请求的计数(一般保持 false) | false |
| RealIpHeader | 反向代理透传的真实 IP 请求头 | X-Real-IP 或 X-Forwarded-For |
| ClientIdHeader | 客户端标识头(如需基于客户端的策略扩展) | X-ClientId |
| HttpStatusCode | 超限返回状态码 | 429 |
| IpWhitelist | IP 白名单(CIDR 支持) | ["127.0.0.1", "192.168.0.0/24"] |
| EndpointWhitelist | 端点白名单(支持通配符) | ["get:/api/license", "*:/api/status"] |
| ClientWhitelist | 客户端白名单 | ["dev-id-1"] |
| QuotaExceededResponse | 超限自定义响应(内容、类型、状态码) | application/json + 消息体 |
| GeneralRules | 通用规则(所有未被特定策略覆盖的请求) | 见下方规则说明 |
| IpRules | 针对某个 IP 的专属规则集合 | 见下方规则说明 |
规则与匹配说明
- Endpoint 格式:
方法:路径,如get:/api/user/*,支持*通配;*:/api/status表示任意方法;*表示所有端点。 - Period 单位:
s秒、m分、h时、d天。例如5s、10m、1h。 - Limit:在一个 Period 窗口内允许的最大请求数(超过则返回 429)。
- 匹配优先级:通常 IP 专属规则 > 端点白名单 > GeneralRules;具体以中间件实现为准。
真实 IP 与反向代理
- 部署在 Nginx/Ingress/反向代理之后时,请确保代理正确设置
X-Real-IP或X-Forwarded-For,并在应用中读取对应头(本示例配置为RealIpHeader = X-Real-IP)。 - 如需全面处理代理头,建议在 ASP.NET Core 中启用
ForwardedHeaders(示例):
csharp
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});分布式与存储选择
- 单实例:使用内存存储(MemoryCacheIpPolicyStore/MemoryCacheRateLimitCounterStore)。
- 多实例:推荐启用 Redis 并使用分布式存储,确保所有实例共享限流计数,避免各自为战。
- 命名空间:本项目为分布式计数器设置了
InstanceName = "rateLimit:",并使用与业务缓存不同的 DB 索引(Index + 2),避免冲突。
响应与可观测性
- 超限时默认返回 429;可通过
QuotaExceededResponse自定义响应体与内容类型。 - 建议记录被限流请求的日志(IP、端点、剩余配额、窗口起止时间),用于审计与调参。
- 可在网关/代理层配合观测(如 Nginx Ingress 限流指标)进行端到端监控。
常见用法片段
按 IP 为登录与验证码分别限流:
json
{
"GeneralRules": [
{ "Endpoint": "post:/auth/login", "Period": "60s", "Limit": 5 },
{ "Endpoint": "get:/auth/captcha*", "Period": "10s", "Limit": 3 }
]
}给某个内网网段放开限制:
json
{
"IpWhitelist": ["10.0.0.0/8", "192.168.0.0/16"]
}为特定 IP 加严限制:
json
{
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "203.0.113.42",
"Rules": [{ "Endpoint": "*", "Period": "1m", "Limit": 30 }]
}
]
}
}最佳实践与注意事项
- 先从少量关键端点(如登录/验证码/搜索)启用,再按监控逐步覆盖,避免“一刀切”。
- 规则要简洁:优先使用 GeneralRules + 少量特例,避免大量细碎规则导致维护困难。
- 置于合适的中间件顺序,尽早拦截无效请求,降低系统消耗。
- 多实例务必启用 Redis 分布式计数;并确认所有实例的时间同步(NTP)。
- 与事务/缓存配合:被限流的写操作不会进入业务层,注意与上游重试策略的配合,避免流量放大;缓存可用于黑名单/频繁来源的快速判定。
- 结合告警:对异常突增的 429 比例设置告警阈值,用于快速回溯与规则调整。
常见问题(FAQ)
- 实际限流无效?
- 检查
UseIpLimitMiddleware()是否生效且顺序正确; - 确认
IpRateLimit.json已加载且路径正确; - 在代理后部署时,确认
RealIpHeader对应头已正确透传真实 IP。
- 检查
- 规则未命中?
- 确认方法与路径大小写/通配符;
get:/api/*与GET:/api/*的区分以配置/实现为准,建议统一小写; - 更具体的规则可能被白名单覆盖;逐条排查优先级。
- 确认方法与路径大小写/通配符;
- 多实例计数不一致?
- 确认已启用 Redis 分布式计数;
- 检查 Redis 连接与实例名/DB 索引是否一致;
- 确保实例间时间同步。
启用步骤小结
- 注册:
services.AddIpStrategyRateLimitService() - 中间件:
app.UseIpLimitMiddleware()(放在合适顺序) - 配置:准备有效的
IpRateLimit.json,按需设置白名单与规则 - 分布式:多实例部署时启用 Redis(
SystemOptions.UseRedisCache = true)共享计数

