483 lines
16 KiB
C#
483 lines
16 KiB
C#
#define FIRST // FIRST ADMIN FIXED SLIDING CONCUR TOKEN FIXED2 JWT
|
|
#if NEVER
|
|
#elif FIXED
|
|
// <snippet_fixed>
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using System.Threading.RateLimiting;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddFixedWindowLimiter(policyName: "fixed", options =>
|
|
{
|
|
options.PermitLimit = 4;
|
|
options.Window = TimeSpan.FromSeconds(12);
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = 2;
|
|
}));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
|
|
.RequireRateLimiting("fixed");
|
|
|
|
app.Run();
|
|
// </snippet_fixed>
|
|
#elif FIXED2
|
|
// <snippet_fixed2>
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
builder.Services.Configure<MyRateLimitOptions>(
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
|
|
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
var fixedPolicy = "fixed";
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
|
|
{
|
|
options.PermitLimit = myOptions.PermitLimit;
|
|
options.Window = TimeSpan.FromSeconds(myOptions.Window);
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = myOptions.QueueLimit;
|
|
}));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", () => Results.Ok($"Fixed Window Limiter {GetTicks()}"))
|
|
.RequireRateLimiting(fixedPolicy);
|
|
|
|
app.Run();
|
|
// </snippet_fixed2>
|
|
#elif SLIDING
|
|
// <snippet_slide>
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using System.Threading.RateLimiting;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
var slidingPolicy = "sliding";
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
|
|
{
|
|
options.PermitLimit = myOptions.PermitLimit;
|
|
options.Window = TimeSpan.FromSeconds(myOptions.Window);
|
|
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = myOptions.QueueLimit;
|
|
}));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}"))
|
|
.RequireRateLimiting(slidingPolicy);
|
|
|
|
app.Run();
|
|
// </snippet_slide>
|
|
#elif CONCUR
|
|
// Quick test 10 users, 9 seconds -> 982 requests, 900 errors
|
|
// <snippet_concur>
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using System.Threading.RateLimiting;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var concurrencyPolicy = "Concurrency";
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
|
|
{
|
|
options.PermitLimit = myOptions.PermitLimit;
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = myOptions.QueueLimit;
|
|
}));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", async () =>
|
|
{
|
|
await Task.Delay(500);
|
|
return Results.Ok($"Concurrency Limiter {GetTicks()}");
|
|
|
|
}).RequireRateLimiting(concurrencyPolicy);
|
|
|
|
app.Run();
|
|
// </snippet_concur>
|
|
#elif TOKEN
|
|
// Quick test 20 users, 20 seconds -> 8965 requests 2,250 errors
|
|
// <snippet_token>
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using System.Threading.RateLimiting;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var tokenPolicy = "token";
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddTokenBucketLimiter(policyName: tokenPolicy, options =>
|
|
{
|
|
options.TokenLimit = myOptions.TokenLimit;
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = myOptions.QueueLimit;
|
|
options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
|
|
options.TokensPerPeriod = myOptions.TokensPerPeriod;
|
|
options.AutoReplenishment = myOptions.AutoReplenishment;
|
|
}));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseRateLimiter();
|
|
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))
|
|
.RequireRateLimiting(tokenPolicy);
|
|
|
|
app.Run();
|
|
// </snippet_token>
|
|
#elif FIRST
|
|
// <snippet_1>
|
|
// <snippet>
|
|
// Preceding code removed for brevity.
|
|
using System.Globalization;
|
|
using System.Net;
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using WebRateLimitAuth;
|
|
using WebRateLimitAuth.Data;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ??
|
|
throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|
options.UseSqlServer(connectionString));
|
|
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
|
|
|
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
|
|
.AddEntityFrameworkStores<ApplicationDbContext>();
|
|
|
|
builder.Services.Configure<MyRateLimitOptions>(
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
|
|
|
|
builder.Services.AddRazorPages();
|
|
builder.Services.AddControllersWithViews();
|
|
|
|
var userPolicyName = "user";
|
|
var helloPolicy = "hello";
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
|
|
builder.Services.AddRateLimiter(limiterOptions =>
|
|
{
|
|
limiterOptions.OnRejected = (context, cancellationToken) =>
|
|
{
|
|
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
|
|
{
|
|
context.HttpContext.Response.Headers.RetryAfter =
|
|
((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
|
|
}
|
|
|
|
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
|
context.HttpContext.RequestServices.GetService<ILoggerFactory>()?
|
|
.CreateLogger("Microsoft.AspNetCore.RateLimitingMiddleware")
|
|
.LogWarning("OnRejected: {GetUserEndPoint}", GetUserEndPoint(context.HttpContext));
|
|
|
|
return new ValueTask();
|
|
};
|
|
|
|
limiterOptions.AddPolicy<string, SampleRateLimiterPolicy>(helloPolicy);
|
|
limiterOptions.AddPolicy(userPolicyName, context =>
|
|
{
|
|
var username = "anonymous user";
|
|
if (context.User.Identity?.IsAuthenticated is true)
|
|
{
|
|
username = context.User.ToString()!;
|
|
}
|
|
|
|
return RateLimitPartition.GetSlidingWindowLimiter(username,
|
|
_ => new SlidingWindowRateLimiterOptions
|
|
{
|
|
PermitLimit = myOptions.PermitLimit,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
Window = TimeSpan.FromSeconds(myOptions.Window),
|
|
SegmentsPerWindow = myOptions.SegmentsPerWindow
|
|
});
|
|
|
|
});
|
|
|
|
limiterOptions.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
|
|
{
|
|
IPAddress? remoteIpAddress = context.Connection.RemoteIpAddress;
|
|
|
|
if (!IPAddress.IsLoopback(remoteIpAddress!))
|
|
{
|
|
return RateLimitPartition.GetTokenBucketLimiter
|
|
(remoteIpAddress!, _ =>
|
|
new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = myOptions.TokenLimit2,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
|
|
TokensPerPeriod = myOptions.TokensPerPeriod,
|
|
AutoReplenishment = myOptions.AutoReplenishment
|
|
});
|
|
}
|
|
|
|
return RateLimitPartition.GetNoLimiter(IPAddress.Loopback);
|
|
});
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseMigrationsEndPoint();
|
|
}
|
|
else
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRouting();
|
|
app.UseRateLimiter();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapRazorPages().RequireRateLimiting(userPolicyName);
|
|
app.MapDefaultControllerRoute();
|
|
|
|
static string GetUserEndPoint(HttpContext context) =>
|
|
$"User {context.User.Identity?.Name ?? "Anonymous"} endpoint:{context.Request.Path}"
|
|
+ $" {context.Connection.RemoteIpAddress}";
|
|
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
|
|
|
|
app.MapGet("/a", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}")
|
|
.RequireRateLimiting(userPolicyName);
|
|
|
|
app.MapGet("/b", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}")
|
|
.RequireRateLimiting(helloPolicy);
|
|
|
|
app.MapGet("/c", (HttpContext context) => $"{GetUserEndPoint(context)} {GetTicks()}");
|
|
|
|
app.Run();
|
|
// </snippet>
|
|
// </snippet_1>
|
|
#elif ADMIN
|
|
// <snippet_adm>
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Primitives;
|
|
using WebRateLimitAuth.Data;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
|
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|
options.UseSqlServer(connectionString));
|
|
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
|
|
|
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
|
|
options.SignIn.RequireConfirmedAccount = true)
|
|
.AddEntityFrameworkStores<ApplicationDbContext>();
|
|
|
|
builder.Services.AddRazorPages();
|
|
builder.Services.AddControllersWithViews();
|
|
|
|
// <snippet_adm2>
|
|
var getPolicyName = "get";
|
|
var postPolicyName = "post";
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
|
|
builder.Services.AddRateLimiter(_ => _
|
|
.AddConcurrencyLimiter(policyName: getPolicyName, options =>
|
|
{
|
|
options.PermitLimit = myOptions.PermitLimit;
|
|
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
options.QueueLimit = myOptions.QueueLimit;
|
|
})
|
|
.AddPolicy(policyName: postPolicyName, partitioner: httpContext =>
|
|
{
|
|
string userName = httpContext.User.Identity?.Name ?? string.Empty;
|
|
|
|
if (!StringValues.IsNullOrEmpty(userName))
|
|
{
|
|
return RateLimitPartition.GetTokenBucketLimiter(userName, _ =>
|
|
new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = myOptions.TokenLimit2,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
|
|
TokensPerPeriod = myOptions.TokensPerPeriod,
|
|
AutoReplenishment = myOptions.AutoReplenishment
|
|
});
|
|
}
|
|
|
|
return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>
|
|
new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = myOptions.TokenLimit,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
|
|
TokensPerPeriod = myOptions.TokensPerPeriod,
|
|
AutoReplenishment = true
|
|
});
|
|
}));
|
|
// </snippet_adm2>
|
|
|
|
var app = builder.Build();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseMigrationsEndPoint();
|
|
}
|
|
else
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRouting();
|
|
app.UseRateLimiter();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
static string GetUserEndPointMethod(HttpContext context) =>
|
|
$"Hello {context.User.Identity?.Name ?? "Anonymous"} " +
|
|
$"Endpoint:{context.Request.Path} Method: {context.Request.Method}";
|
|
|
|
app.MapGet("/test", (HttpContext context) => $"{GetUserEndPointMethod(context)}")
|
|
.RequireRateLimiting(getPolicyName);
|
|
|
|
app.MapRazorPages().RequireRateLimiting(getPolicyName)
|
|
.RequireRateLimiting(postPolicyName);
|
|
|
|
app.MapDefaultControllerRoute();
|
|
|
|
app.Run();
|
|
// </snippet_adm>
|
|
#elif JWT
|
|
// <snippet_jwt>
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.Extensions.Primitives;
|
|
using WebRateLimitAuth.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddAuthorization();
|
|
builder.Services.AddAuthentication("Bearer").AddJwtBearer();
|
|
|
|
var myOptions = new MyRateLimitOptions();
|
|
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
|
|
var jwtPolicyName = "jwt";
|
|
|
|
builder.Services.AddRateLimiter(limiterOptions =>
|
|
{
|
|
limiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
|
limiterOptions.AddPolicy(policyName: jwtPolicyName, partitioner: httpContext =>
|
|
{
|
|
var accessToken = httpContext.Features.Get<IAuthenticateResultFeature>()?
|
|
.AuthenticateResult?.Properties?.GetTokenValue("access_token")?.ToString()
|
|
?? string.Empty;
|
|
|
|
if (!StringValues.IsNullOrEmpty(accessToken))
|
|
{
|
|
return RateLimitPartition.GetTokenBucketLimiter(accessToken, _ =>
|
|
new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = myOptions.TokenLimit2,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
|
|
TokensPerPeriod = myOptions.TokensPerPeriod,
|
|
AutoReplenishment = myOptions.AutoReplenishment
|
|
});
|
|
}
|
|
|
|
return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>
|
|
new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = myOptions.TokenLimit,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
|
QueueLimit = myOptions.QueueLimit,
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
|
|
TokensPerPeriod = myOptions.TokensPerPeriod,
|
|
AutoReplenishment = true
|
|
});
|
|
});
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseAuthorization();
|
|
app.UseRateLimiter();
|
|
|
|
app.MapGet("/", () => "Hello, World!");
|
|
|
|
app.MapGet("/jwt", (HttpContext context) => $"Hello {GetUserEndPointMethod(context)}")
|
|
.RequireRateLimiting(jwtPolicyName)
|
|
.RequireAuthorization();
|
|
|
|
app.MapPost("/post", (HttpContext context) => $"Hello {GetUserEndPointMethod(context)}")
|
|
.RequireRateLimiting(jwtPolicyName)
|
|
.RequireAuthorization();
|
|
|
|
app.Run();
|
|
|
|
static string GetUserEndPointMethod(HttpContext context) =>
|
|
$"Hello {context.User.Identity?.Name ?? "Anonymous"} " +
|
|
$"Endpoint:{context.Request.Path} Method: {context.Request.Method}";
|
|
// </snippet_jwt>
|
|
#endif
|