I've recently been working on a project using the ZhonTai Admin.Core framework. While reviewing the configuration files, I noticed that it has built-in IP rate limiting functionality, implemented with the AspNetCoreRateLimit component and Redis. I did some research and wanted to document how to use it and the underlying principles.
Enable Rate Limiting
Rate limiting in Admin.Core is disabled by default and must be manually enabled in two places.
Enable the rate limiting switch in appconfig.json:
{
"rateLimit": true
}
Then, configure the rate-limiting storage method as Redis in cacheconfig.json:
{
"typeRateLimit": "Redis"
}
Without Redis, the rate-limiting counters are stored in memory. This works fine for a single instance, but if multiple instances are deployed, each one tracks its own count independently, effectively rendering the rate limiting useless.
Configure Rate Limiting Rules
Rate limiting rules are defined in the IpRateLimiting node of appsettings.json:
{
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"HttpStatusCode": 429,
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "post:/api/admin/auth/login",
"Period": "1m",
"Limit": 5
}
]
}
}
Here are a few key configuration points:
Set EnableEndpointRateLimiting to true. Otherwise, all rules apply globally, and you won't be able to rate limit specific endpoints individually.
It is recommended to keep StackBlockedRequests as false. If set to true, repeated requests that are rate-limited will still count toward the access limit, potentially causing the client to remain blocked indefinitely.
For RealIpHeader, if you have a reverse proxy like Nginx or Caddy in front of your service, you need to configure it to match the real IP header passed by the proxy. Otherwise, the rate limiting will apply to the proxy server's IP, causing all users to share the same counter.
The format for Endpoint is HTTP method:path, where * represents all endpoints. The configuration above means: a global limit of 10 requests per second, and a limit of 5 requests per minute for the login endpoint.
How It Works
The principle is not complex. When each request comes in, the AspNetCoreRateLimit middleware will:
- Obtain the client's IP address
- Generate a Redis key based on the IP + request path
- Perform an increment operation on this key and set an expiration time (which is the Period you configured)
- If the count exceeds the Limit, directly return a 429 status code, and the request will not reach the Controller
Since the counters are stored in Redis, multiple instances share the same data, so regardless of which instance the request is load-balanced to, the rate limiting applies uniformly.
Behavior After Rate Limiting Is Triggered
After rate limiting is triggered, the API returns an HTTP 429 status code. The Admin.Core frontend also handles this by displaying a prompt to inform the user that their requests are too frequent.
If you want to customize the response returned after rate limiting, you can add a QuotaExceededResponse to the configuration:
{
"IpRateLimiting": {
"QuotaExceededResponse": {
"Content": "{{ \"message\": \"请求太频繁,请稍后再试\", \"details\": \"限流规则: 每 {1} 最多 {0} 次,请 {2} 秒后重试\" }}",
"ContentType": "application/json",
"StatusCode": 429
}
}
}
A Common Pitfall
The order in which middleware is registered is important. UseIpRateLimiting() must be placed after UseStaticFiles(). Otherwise, requests for static files (js, css, images) will also be counted toward the access rate limit, causing the limit to be reached quickly.
How to Integrate Without Admin.Core
If you're not using Admin.Core and want to integrate this rate-limiting solution into your own .NET WebApi project, the core process involves three steps:
Install dependencies:
dotnet add package AspNetCoreRateLimit
dotnet add package AspNetCoreRateLimit.Redis
dotnet add package StackExchange.Redis
Register service:
builder.Services.AddOptions();
builder.Services.Configure<IpRateLimitOptions>(
builder.Configuration.GetSection("IpRateLimiting"));
var redisOptions = ConfigurationOptions.Parse(
builder.Configuration.GetConnectionString("Redis"));
builder.Services.AddSingleton<IConnectionMultiplexer>(
_ => ConnectionMultiplexer.Connect(redisOptions));
builder.Services.AddRedisRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
Using middleware:
app.UseStaticFiles();
app.UseIpRateLimiting(); // 放在 UseStaticFiles 后面
The rule format in the configuration file is the same as the one above.
Another Option
Actually, starting from .NET 7, Microsoft has included built-in rate limiting middleware Microsoft.AspNetCore.RateLimiting, which supports four algorithms: fixed window, sliding window, token bucket, and concurrency limit. If you need to implement distributed rate limiting with Redis, you can use the community-maintained RedisRateLimiting NuGet package.
However, Admin.Core uses AspNetCoreRateLimit. This component was released earlier and offers more flexible configuration, with nearly three thousand stars on GitHub. If you are already using Admin.Core, just use its built-in implementation; there is no need to switch.
Comments (0)
No comments yet. Be the first to leave one!