Rate Limiting en .NET: Protegiendo tu API desde el Principio
En el mundo del desarrollo de software moderno, la seguridad y el rendimiento no deben ser una ocurrencia tardía, sino pilares fundamentales desde el inicio del proyecto. Ignorar estos aspectos puede llevar a vulnerabilidades críticas y a un deterioro del rendimiento que afecte directamente la experiencia del usuario y, en última instancia, la viabilidad de un producto. Una de las estrategias más efectivas y sencillas para mitigar estos riesgos es la implementación de Rate Limiting.
Este artículo es el primero de una serie enfocada en la seguridad, donde exploraremos diversas acciones, tanto a nivel de código como de infraestructura, para proteger nuestras aplicaciones.
Qué es el Rate Limiting y por qué es tan importante? 🔐
El Rate Limiting, o limitación de tasa, es una práctica de seguridad que controla la cantidad de solicitudes que un cliente puede hacer a un servidor dentro de un período de tiempo determinado.
La analogía de un fusible en el hogar es perfecta: cuando el consumo de energía excede un límite predefinido, el fusible salta para evitar daños mayores. En el software, el Rate Limiting actúa de manera similar, protegiendo nuestros recursos de un uso excesivo.
¿Por qué es crucial para una API o servicio web?
- Prevenir Ataques de Denegación de Servicio (DoS): Un atacante puede saturar un endpoint con miles de solicitudes por segundo, lo que consume recursos de la base de datos y de la CPU, volviendo el servicio inaccesible para los usuarios legítimos.
- Controlar Costos: Si tu servicio depende de APIs de terceros que cobran por uso, o si tu infraestructura tiene costos asociados al tráfico (como el ancho de banda), el Rate Limiting te ayuda a evitar facturas inesperadas causadas por un uso abusivo.
- Asegurar la Equidad y la Estabilidad: Garantiza que un usuario o aplicación no acapare todos los recursos disponibles, asegurando una experiencia equitativa y estable para todos los clientes.
Es importante recordar que el Rate Limiting no es una solución de seguridad única. Es parte de una estrategia de defensa en profundidad que debe complementarse con otras medidas, como el uso de un WAF (Web Application Firewall), bloqueo de IPs maliciosas y detección de bots.
Implementando Rate Limiting en .NET 🚀
Desde .NET 8, el middleware de Rate Limiting es una característica integrada en el framework, lo que simplifica enormemente su implementación. Para versiones anteriores, puedes utilizar el paquete NuGet Microsoft.AspNetCore.RateLimiting.
La configuración se realiza en el archivo Program.cs de tu proyecto. El primer paso es añadir el servicio:
builder.Services.AddRateLimiter(options =>
{
// Aquí se definen las políticas de limitación
});
El objeto options de RateLimiterOptionsExtensions nos permite definir varias políticas de limitación.
Tipos de Políticas de Rate Limiting en .NET
El framework ofrece cuatro algoritmos de control principales:
- Fixed Window Limiter: Permite un número fijo de solicitudes dentro de una ventana de tiempo. Una vez que la ventana de tiempo se agota, los contadores se reinician.
- Sliding Window Limiter: Similar al anterior, pero la ventana de tiempo se divide en segmentos. A medida que pasa el tiempo, los segmentos más antiguos se descartan y se abren nuevos, lo que ofrece un control más granular y suave del tráfico.
- Token Bucket Limiter: El límite se basa en un “cubo de tokens”. El cubo se llena a una tasa constante y cada solicitud consume un token. Si el cubo está vacío, la solicitud es rechazada. Esto permite ráfagas de tráfico controladas.
- Concurrency Limiter: Limita el número de solicitudes simultáneas que puede manejar el servidor. Las solicitudes adicionales se ponen en cola o se rechazan. Este método no se basa en un período de tiempo, sino en la capacidad instantánea del sistema.
Configuración de las Políticas
Aquí tienes un ejemplo de cómo configurar cada una de estas políticas en tu código:
builder.Services.AddRateLimiter(options =>
{
// Política de Ventana Fija
options.AddFixedWindowLimiter("FixedPolicy", opt =>
{
opt.Window = TimeSpan.FromSeconds(20);
opt.PermitLimit = 2;
opt.QueueLimit = 0;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
// Política de Ventana Deslizante
options.AddSlidingWindowLimiter("SlidingPolicy", opt =>
{
opt.Window = TimeSpan.FromSeconds(20);
opt.SegmentsPerWindow = 2;
opt.PermitLimit = 2;
opt.QueueLimit = 0;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
// Política de Cubo de Tokens
options.AddTokenBucketLimiter("TokenPolicy", opt =>
{
opt.TokenLimit = 2;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 0;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(20);
opt.TokensPerPeriod = 60;
opt.AutoReplenishment = true;
});
// Política de Concurrencia
options.AddConcurrencyLimiter("ConcurrencyPolicy", opt =>
{
opt.PermitLimit = 10;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2;
});
// Configuración global para rechazos
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
Una de las decisiones más importantes es la configuración de la cola de espera (QueueLimit y QueueProcessingOrder). Si bien una cola puede manejar ráfagas de tráfico, también consume recursos de memoria y puede causar latencia en las respuestas. En muchos casos, rechazar la solicitud de inmediato (QueueLimit = 0) es preferible para mantener la estabilidad y evitar que un ataque DoS consuma todos los recursos.
Por defecto, si una solicitud es rechazada, el middleware devuelve un código de estado 503 (Service Unavailable). Sin embargo, en un contexto de API, es más apropiado devolver un 429 (Too Many Requests) para informar al cliente que ha excedido el límite de llamadas, lo cual se configura con options.RejectionStatusCode.
Aplicando las Políticas a los Endpoints
Una vez que las políticas están definidas, solo queda aplicarlas a los endpoints. En una API Minimal, esto se hace fácilmente con el método RequireRateLimiting().
protected override RouteHandlerBuilder Configure(RouteHandlerBuilder builder)
=> builder
// ... otras configuraciones
.WithName("GetAvenger")
.WithTags("Avengers")
.RequireRateLimiting("FixedPolicy");
Aquí, hemos aplicado la política FixedPolicy al endpoint GetAvenger.
Resumen
El Rate Limiting es una medida de seguridad fundamental para proteger las aplicaciones web de un uso excesivo y malicioso. Al implementar esta técnica, prevenimos ataques de denegación de servicio (DoS), controlamos los costos de nuestra infraestructura y garantizamos la estabilidad del servicio. En .NET 8, el middleware integrado facilita su configuración, permitiendo definir diferentes políticas (Fixed, Sliding, Token Bucket y Concurrency) y aplicarlas de manera granular a los endpoints. Esta práctica, combinada con otras medidas de seguridad, es clave para construir productos digitales robustos y rentables.
Podéis ver el ejemplo en mi repositorio de Github
Happy Codding