fix bug 63687: added rate limiting for api

This commit is contained in:
Alexey Bannov 2023-08-18 18:47:23 +03:00
parent ceb2a85979
commit 720a2817f8
5 changed files with 99 additions and 1 deletions

View File

@ -26,6 +26,8 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.3" />
<PackageReference Include="RedisRateLimiting" Version="1.0.11" />
<PackageReference Include="RedisRateLimiting.AspNetCore" Version="1.0.8" />
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="9.1.0" />
<PackageReference Include="StackExchange.Redis.Extensions.Newtonsoft" Version="9.1.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Role = ASC.Common.Security.Authorizing.Role;
namespace ASC.Api.Core.Auth;
[Scope]

View File

@ -100,6 +100,88 @@ public abstract class BaseStartup
}
});
var redisOptions = _configuration.GetSection("Redis").Get<RedisConfiguration>().ConfigurationOptions;
var connectionMultiplexer = ConnectionMultiplexer.Connect(redisOptions);
services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userId = httpContext?.User?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
if (userId == null)
{
return RateLimitPartition.GetNoLimiter("no_limiter");
}
var permitLimit = 1500;
string partitionKey;
partitionKey = $"fw_{userId}";
return RedisRateLimitPartition.GetFixedWindowRateLimiter(partitionKey, key => new RedisFixedWindowRateLimiterOptions
{
PermitLimit = permitLimit,
Window = TimeSpan.FromMinutes(1),
ConnectionMultiplexerFactory = () => connectionMultiplexer
});
}),
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userId = httpContext?.User?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
string partitionKey;
int permitLimit;
if (userId == null)
{
return RateLimitPartition.GetNoLimiter("no_limiter");
}
if (String.Compare(httpContext.Request.Method, "GET", true) == 0)
{
permitLimit = 50;
partitionKey = $"cr_read_{userId}";
}
else
{
permitLimit = 15;
partitionKey = $"cr_write_{userId}";
}
return RedisRateLimitPartition.GetConcurrencyRateLimiter(partitionKey, key => new RedisConcurrencyRateLimiterOptions
{
PermitLimit = permitLimit,
QueueLimit = 0,
ConnectionMultiplexerFactory = () => connectionMultiplexer
});
}
));
options.AddPolicy("sensitive_api", httpContext => {
var userId = httpContext?.User?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
if (userId == null)
{
return RateLimitPartition.GetNoLimiter("no_limiter");
}
var permitLimit = 5;
var partitionKey = $"sensitive_api_{userId}";
return RedisRateLimitPartition.GetFixedWindowRateLimiter(partitionKey, key => new RedisFixedWindowRateLimiterOptions
{
PermitLimit = permitLimit,
Window = TimeSpan.FromMinutes(1),
ConnectionMultiplexerFactory = () => connectionMultiplexer
});
});
options.OnRejected = (context, ct) => RateLimitMetadata.OnRejected(context.HttpContext, context.Lease, ct);
});
services.AddScoped<EFLoggerFactory>();
services.AddBaseDbContextPool<AccountLinkContext>()
@ -311,6 +393,8 @@ public abstract class BaseStartup
app.UseAuthentication();
app.UseRateLimiter();
app.UseAuthorization();
app.UseCultureMiddleware();
@ -346,6 +430,8 @@ public abstract class BaseStartup
await context.Response.WriteAsync($"{Environment.MachineName} running {CustomHealthCheck.Running}");
});
});
}
public void ConfigureContainer(ContainerBuilder builder)

View File

@ -41,6 +41,7 @@ global using System.Text.Encodings.Web;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;
global using System.Threading.RateLimiting;
global using System.Web;
global using System.Xml.Linq;
@ -150,6 +151,10 @@ global using NLog.Web;
global using RabbitMQ.Client;
global using RedisRateLimiting;
global using RedisRateLimiting.AspNetCore;
global using StackExchange.Redis;
global using StackExchange.Redis.Extensions.Core.Configuration;
global using StackExchange.Redis.Extensions.Newtonsoft;

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.AspNetCore.RateLimiting;
namespace ASC.People.Api;
public class UserController : PeopleControllerBase
@ -1221,7 +1223,8 @@ public class UserController : PeopleControllerBase
/// <requiresAuthorization>false</requiresAuthorization>
[AllowNotPayment]
[AllowAnonymous]
[HttpPost("password")]
[HttpPost("password")]
[EnableRateLimiting("sensitive_api")]
public async Task<object> SendUserPasswordAsync(MemberRequestDto inDto)
{
if (_authContext.IsAuthenticated)