Bagikan melalui


Middleware batas laju di ASP.NET Core

Oleh Arvin Kahbazi, Maarten Balliauw, dan Rick Anderson

Middleware Microsoft.AspNetCore.RateLimiting menyediakan middleware untuk pembatasan laju. Aplikasi mengonfigurasi kebijakan pembatasan tarif lalu melampirkan kebijakan ke titik akhir. Aplikasi yang menggunakan pembatasan tarif harus diuji dan ditinjau dengan cermat sebelum disebarkan. Lihat Menguji endpoint dengan pembatasan kecepatan dalam artikel ini untuk informasi lebih lanjut.

Untuk pengenalan pembatasan laju, lihat Middleware pembatasan laju.

Mengapa menggunakan pembatasan tarif

Pembatasan tarif dapat digunakan untuk mengelola alur permintaan masuk ke aplikasi. Alasan utama untuk menerapkan pembatasan tarif:

  • Mencegah Penyalahgunaan: Pembatasan tarif membantu melindungi aplikasi dari penyalahgunaan dengan membatasi jumlah permintaan yang dapat dibuat pengguna atau klien dalam periode waktu tertentu. Ini sangat penting untuk API publik.
  • Memastikan Penggunaan yang Adil: Dengan menetapkan batas, semua pengguna memiliki akses yang adil ke sumber daya, mencegah pengguna memonopoli sistem.
  • Melindungi Sumber Daya: Pembatasan laju membantu mencegah kelebihan beban server dengan mengontrol jumlah permintaan yang dapat diproses, sehingga melindungi sumber daya backend dari kewalahan.
  • Meningkatkan keamanan: Ini dapat mengurangi risiko serangan Denial of Service (DoS) dengan membatasi tingkat di mana permintaan diproses, sehingga lebih sulit bagi penyerang untuk membanjiri sistem.
  • Meningkatkan Performa: Dengan mengontrol tingkat permintaan masuk, performa dan responsivitas aplikasi yang optimal dapat dipertahankan, memastikan pengalaman pengguna yang lebih baik.
  • Cost Management: Untuk layanan yang dikenakan biaya berdasarkan penggunaan, pembatasan tarif dapat membantu mengelola dan memprediksi pengeluaran dengan mengontrol volume permintaan yang diproses.

Menerapkan pembatasan tarif dalam aplikasi ASP.NET Core dapat membantu menjaga stabilitas, keamanan, dan performa, memastikan layanan yang andal dan efisien untuk semua pengguna.

Mencegah Serangan DDoS

Meskipun pembatasan tarif dapat membantu mengurangi risiko serangan Denial of Service (DoS) dengan membatasi tingkat di mana permintaan diproses, ini bukan solusi komprehensif untuk serangan Distributed Denial of Service (DDoS). Serangan DDoS melibatkan beberapa sistem yang membanjiri aplikasi dengan banjir permintaan, sehingga sulit ditangani dengan pembatasan laju saja.

Untuk perlindungan DDoS yang kuat, pertimbangkan untuk menggunakan layanan perlindungan DDoS komersial. Layanan ini menawarkan fitur lanjutan seperti:

  • Analisis Lalu Lintas: Pemantauan dan analisis berkelanjutan lalu lintas masuk untuk mendeteksi dan mengurangi serangan DDoS secara real time.
  • Skalabilitas: Kemampuan untuk menangani serangan skala besar dengan mendistribusikan lalu lintas di beberapa server dan pusat data.
  • Mitigasi otomatis: Mekanisme respons otomatis untuk memblokir lalu lintas berbahaya dengan cepat tanpa intervensi manual.
  • Global Network: Jaringan global server untuk menyerap dan mengurangi serangan lebih dekat ke sumbernya.
  • Pembaruan Konstan: Layanan komersial terus melacak dan memperbarui mekanisme perlindungan mereka untuk beradaptasi dengan ancaman baru dan berkembang.

Saat menggunakan layanan hosting cloud, perlindungan DDoS biasanya tersedia sebagai bagian dari solusi hosting, seperti Azure Web Application Firewall, AWS Shield atau Google Cloud Armor. Perlindungan khusus tersedia sebagai Web Application Firewalls (WAF) atau sebagai bagian dari solusi CDN seperti Cloudflare atau Akamai Kona Site Defender

Menerapkan layanan perlindungan DDoS komersial bersama dengan pembatasan tarif dapat memberikan strategi pertahanan yang komprehensif, memastikan stabilitas, keamanan, dan performa aplikasi.

Gunakan Middleware Pembatasan Laju

Langkah-langkah berikut menunjukkan cara menggunakan middleware pembatasan laju di aplikasi ASP.NET Core:

  1. Mengonfigurasi layanan pembatasan tarif.

Dalam file Program.cs, konfigurasikan layanan pembatasan tarif dengan menambahkan kebijakan pembatasan tarif yang sesuai. Kebijakan dapat didefinisikan sebagai kebijakan global atau kebijakan bernama. Contoh berikut mengizinkan 10 permintaan per menit berdasarkan pengguna (identitas) atau secara global:

builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
            factory: partition => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 10,
                QueueLimit = 0,
                Window = TimeSpan.FromMinutes(1)
            }));
});

Kebijakan yang diberi nama perlu diterapkan secara eksplisit pada halaman atau titik akhir. Contoh berikut menambahkan kebijakan pembatas jendela tetap bernama "fixed" yang akan kita tambahkan ke titik akhir nanti:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", opt =>
    {
        opt.PermitLimit = 4;
        opt.Window = TimeSpan.FromSeconds(12);
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 2;
    });
});

var app = builder.Build();

Pembatas global berlaku untuk semua titik akhir secara otomatis saat dikonfigurasi melalui opsi . GlobalLimiter.

  1. Aktifkan middleware pembatasan kecepatan

    Dalam file Program.cs, aktifkan middleware pembatasan tarif dengan memanggil UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

Menerapkan kebijakan pembatasan tarif ke titik akhir atau halaman

Menerapkan pembatasan tarif ke Titik Akhir WebAPI

Terapkan kebijakan bernama ke titik akhir atau grup, misalnya:


app.MapGet("/api/resource", () => "This endpoint is rate limited")
   .RequireRateLimiting("fixed"); // Apply specific policy to an endpoint

Menerapkan pembatasan laju pada Pengontrol MVC

Terapkan kebijakan pembatasan tarif yang dikonfigurasi ke titik akhir tertentu atau secara global. Misalnya, untuk menerapkan kebijakan "tetap" ke semua titik akhir pengontrol:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireRateLimiting("fixed");
});

Menerapkan pembatasan tarif ke aplikasi Blazor sisi server

Untuk menetapkan pembatasan tarif untuk semua komponen Razor yang dapat dirutekan aplikasi, tentukan RequireRateLimiting dengan nama kebijakan pembatasan tarif pada panggilan MapRazorComponents dalam file Program. Dalam contoh berikut, kebijakan pembatasan tarif bernama "policy" diterapkan:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .RequireRateLimiting("policy");

Untuk menetapkan kebijakan untuk sebuah komponen Razor yang dapat dirutekan atau folder berisi komponen melalui sebuah file _Imports.razor, atribut [EnableRateLimiting] diterapkan dengan nama kebijakan. Dalam contoh berikut, kebijakan pembatasan tarif bernama "override" diterapkan. Kebijakan ini menggantikan semua kebijakan yang saat ini diterapkan pada endpoint. Pembatas global tetap berfungsi pada titik akhir dengan penerapan atribut ini.

@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]

<h1>Counter</h1>

Atribut [EnableRateLimiting] hanya diterapkan ke komponen yang dapat dirutekan atau folder komponen melalui file _Imports.razor jika RequireRateLimitingtidak dipanggil pada MapRazorComponents.

Atribut [DisableRateLimiting] digunakan untuk menonaktifkan pembatasan laju untuk komponen yang dapat dirutekan atau folder komponen melalui file _Imports.razor.

Algoritma pembatas laju

Kelas RateLimiterOptionsExtensions ini menyediakan metode ekstensi berikut untuk pembatasan tarif:

Pembatas tetap, geser, dan token semuanya membatasi jumlah maksimum permintaan dalam periode waktu tertentu. Pembatas konkurensi hanya membatasi jumlah permintaan bersamaan dan tidak membatasi jumlah permintaan dalam jangka waktu tertentu. Biaya titik akhir harus dipertimbangkan saat memilih pembatas. Biaya titik akhir mencakup sumber daya yang digunakan, misalnya, waktu, akses data, CPU, dan I/O.

Pembatas jendela tetap

Metode ini AddFixedWindowLimiter menggunakan jendela waktu tetap untuk membatasi permintaan. Saat jendela waktu kedaluwarsa, jendela waktu baru dimulai dan batas permintaan diatur ulang.

Pertimbangkan kode berikut:

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();

Kode sebelumnya:

Aplikasi harus menggunakan Konfigurasi untuk mengatur opsi pembatas. Kode berikut memperbarui kode sebelumnya menggunakan MyRateLimitOptions untuk konfigurasi:

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();

UseRateLimiter harus dipanggil setelah UseRouting ketika pembatasan kecepatan API titik akhir tertentu digunakan. Misalnya, jika [EnableRateLimiting] atribut digunakan, UseRateLimiter harus dipanggil setelah UseRouting. Saat hanya memanggil pembatas global, UseRateLimiter dapat dipanggil sebelum UseRouting.

Pengatur jendela geser

Algoritma dengan jendela geser

  • Mirip dengan pembatas jendela tetap tetapi menambahkan segmen per jendela. Jendela menggeser satu segmen setiap selang segmen. Interval segmen adalah (waktu jendela)/(segmen per jendela).
  • Membatasi jumlah permintaan jendela ke permitLimit permintaan.
  • Setiap jendela waktu dibagi dalam n segmen per jendela.
  • Permintaan yang diambil dari segmen waktu yang sudah kedaluwarsa pada satu jendela sebelumnya (n segmen sebelum segmen saat ini) ditambahkan ke segmen saat ini. Kami merujuk ke segmen waktu yang paling kedaluwarsa dari satu jendela sebelumnya sebagai segmen yang kedaluwarsa.

Pertimbangkan tabel berikut yang memperlihatkan pembatas jendela geser dengan jendela 30 detik, tiga segmen per jendela, dan batas 100 permintaan:

  • Baris atas dan kolom pertama memperlihatkan segmen waktu.
  • Baris kedua memperlihatkan permintaan yang masih tersedia. Permintaan yang tersisa dihitung sebagai permintaan yang tersedia dikurangi permintaan yang diproses ditambah permintaan daur ulang.
  • Permintaan pada setiap waktu bergerak di sepanjang garis biru diagonal.
  • Mulai dari waktu 30, permintaan dari segmen waktu yang sudah kedaluwarsa ditambahkan kembali ke dalam batas permintaan, seperti yang ditunjukkan pada garis merah.

Tabel memperlihatkan permintaan, batas, dan slot daur ulang

Tabel berikut ini memperlihatkan data dalam grafik sebelumnya dalam format yang berbeda. Kolom Tersedia memperlihatkan permintaan yang tersedia dari segmen sebelumnya (Dibawa dari baris sebelumnya). Baris pertama menunjukkan 100 permintaan yang tersedia karena belum ada segmen sebelumnya.

Waktu Tersedia Diambil Didaur ulang dari produk yang sudah kedaluwarsa Melanjutkan
0 100 20 0 80
10 80 30 0 50
20 50 40 0 10
30 10 30 20 0
40 0 10 30 20
50 20 10 40 50
60 50 35 30 45

Kode berikut menggunakan pembatas laju jendela geser:

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();

Pembatas wadah token

Pembatas wadah token mirip dengan pembatas jendela geser, tetapi daripada menambahkan kembali permintaan yang diambil dari segmen yang kedaluwarsa, jumlah token tetap ditambahkan setiap periode pengisian ulang. Token yang ditambahkan setiap segmen tidak dapat meningkatkan token yang tersedia ke angka yang lebih tinggi dari batas wadah token. Tabel berikut menunjukkan pembatas wadah token dengan batas 100 token dan periode pengisian ulang 10 detik.

Waktu Tersedia Diambil Ditambahkan Melanjutkan
0 100 20 0 80
10 80 10 20 90
20 90 5 15 100
30 100 30 20 90
40 90 6 16 100
50 100 40 20 80
60 80 50 20 50

Kode berikut menggunakan pembatas wadah 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();

Ketika AutoReplenishment diatur ke true, timer internal mengisi ulang token setiap ReplenishmentPeriod; ketika diatur ke false, aplikasi harus memanggil TryReplenish pembatas.

Pembatas konkurensi

Pembatas konkurensi membatasi jumlah permintaan bersamaan. Setiap permintaan mengurangi batas konkurensi satu per satu. Ketika permintaan selesai, batas ditingkatkan sebanyak satu. Tidak seperti pembatas permintaan lain yang membatasi jumlah total permintaan untuk periode tertentu, pembatas konkurensi hanya membatasi jumlah permintaan bersamaan dan tidak membatasi jumlah permintaan dalam jangka waktu tertentu.

Kode berikut menggunakan pembatas konkurensi:

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();

Partisi Pembatasan Kecepatan

Partisi pembatasan laju membagi lalu lintas menjadi "ember" terpisah yang masing-masing mendapatkan penghitung batas laju mereka sendiri. Ini memungkinkan kontrol yang lebih terperinci daripada satu penghitung global. "Bagian" partisi didefinisikan oleh kunci yang berbeda (seperti ID pengguna, alamat IP, atau kunci API).

Manfaat Pemartisian

  • Fairness: Satu pengguna tidak dapat menggunakan seluruh batas tarif untuk semua orang
  • Granularitas: Batas yang berbeda untuk pengguna/sumber daya yang berbeda
  • Keamanan : Perlindungan yang lebih baik terhadap penyalahgunaan yang ditargetkan
  • Layanan Berjenjang : Dukungan untuk tingkat layanan dengan batas yang berbeda

Pembatasan tarif yang dipartisi memberi Anda kontrol mendetail atas cara Anda mengelola lalu lintas API sambil memastikan alokasi sumber daya yang adil.

Menurut Alamat IP

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 50,
            Window = TimeSpan.FromMinutes(1)
        }));

Oleh Pengguna Identity

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        }));

Berdasarkan Kunci API

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
    string apiKey = httpContext.Request.Headers["X-API-Key"].ToString() ?? "no-key";

    // Different limits based on key tier
    return apiKey switch
    {
        "premium-key" => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 1000,
                Window = TimeSpan.FromMinutes(1)
            }),

        _ => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: apiKey,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }),
    };
});

Menurut Jalur Titik Akhir

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
    string path = httpContext.Request.Path.ToString();

    // Different limits for different paths
    if (path.StartsWith("/api/public"))
    {
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: $"{httpContext.Connection.RemoteIpAddress}-public",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 30,
                Window = TimeSpan.FromSeconds(10)
            });
    }

    return RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        });
});

Membuat pembatas berantai

API CreateChained memungkinkan penerusan beberapa PartitionedRateLimiter yang dikombinasikan menjadi satu PartitionedRateLimiter. Pembatas gabungan menjalankan semua pembatas input secara berurutan.

Kode berikut menggunakan CreateChained:

using System.Globalization;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(_ =>
{
    _.OnRejected = async (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;
        await context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.", cancellationToken);
    };
    _.GlobalLimiter = PartitionedRateLimiter.CreateChained(
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            var userAgent = httpContext.Request.Headers.UserAgent.ToString();

            return RateLimitPartition.GetFixedWindowLimiter
            (userAgent, _ =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 4,
                    Window = TimeSpan.FromSeconds(2)
                });
        }),
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            var userAgent = httpContext.Request.Headers.UserAgent.ToString();
            
            return RateLimitPartition.GetFixedWindowLimiter
            (userAgent, _ =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 20,    
                    Window = TimeSpan.FromSeconds(30)
                });
        }));
});

var app = builder.Build();
app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"));

app.Run();

Untuk informasi selengkapnya, lihat kode sumber CreateChained

Memilih apa yang terjadi ketika permintaan dibatasi oleh batas kecepatan

Untuk kasus sederhana, Anda hanya dapat mengatur kode status:

builder.Services.AddRateLimiter(options =>
{
    // Set a custom status code for rejections
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Rate limiter configuration...
});

Pendekatan yang paling umum adalah mendaftarkan panggilan balik OnRejected saat mengonfigurasi pembatasan tarif:

builder.Services.AddRateLimiter(options =>
{
    // Rate limiter configuration...

    options.OnRejected = async (context, cancellationToken) =>
    {
        // Custom rejection handling logic
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        context.HttpContext.Response.Headers["Retry-After"] = "60";

        await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);

        // Optional logging
        logger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
            context.HttpContext.Connection.RemoteIpAddress);
    };
});

Opsi lain adalah mengantrekan permintaan:

Permintaan Antrean

Dengan antrean diaktifkan, ketika permintaan melebihi batas laju, permintaan ditempatkan dalam antrean di mana permintaan menunggu hingga izin tersedia atau sampai waktu tunggu habis. Permintaan diproses sesuai dengan urutan antrean yang dapat dikonfigurasi.

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", options =>
    {
        options.PermitLimit = 10;           // Allow 10 requests
        options.Window = TimeSpan.FromSeconds(10);  // Per 10-second window
        options.QueueLimit = 5;             // Queue up to 5 additional requests
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Process oldest requests first
        options.AutoReplenishment = true; // Default: automatically replenish permits
    });
});

EnableRateLimiting dan atribut DisableRateLimiting

Atribut [EnableRateLimiting] dan [DisableRateLimiting] dapat diterapkan ke Pengontrol, metode tindakan, atau Razor Halaman. Untuk Razor Halaman, atribut harus diterapkan ke Razor Halaman dan bukan penanganan halaman. Misalnya, [EnableRateLimiting] tidak dapat diterapkan ke OnGet, , OnPostatau handler halaman lainnya.

Atribut [DisableRateLimiting]menonaktifkan pembatasan tingkat ke Controller, metode aksi, atau Razor Halaman, terlepas dari pembatas laju bernama atau pembatas global yang diterapkan. Misalnya, pertimbangkan kode berikut yang memanggil RequireRateLimiting untuk menerapkan fixedPolicy pembatasan laju ke semua endpoint pengontrol.

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

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 slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
    .AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
    {
        options.PermitLimit = myOptions.SlidingPermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();
app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);

app.Run();

Dalam kode berikut, [DisableRateLimiting] menonaktifkan pembatasan tarif dan menimpa [EnableRateLimiting("fixed")] yang diterapkan ke Home2Controller dan dipanggil dalam app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) pada Program.cs.

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
    private readonly ILogger<Home2Controller> _logger;

    public Home2Controller(ILogger<Home2Controller> logger)
    {
        _logger = logger;
    }

    public ActionResult Index()
    {
        return View();
    }

    [EnableRateLimiting("sliding")]
    public ActionResult Privacy()
    {
        return View();
    }

    [DisableRateLimiting]
    public ActionResult NoLimit()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

Dalam kode sebelumnya, [EnableRateLimiting("sliding")]tidak diterapkan ke metode aksi Privacy karena Program.cs dipanggil app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy).

Pertimbangkan kode berikut yang tidak memanggil RequireRateLimiting di MapRazorPages atau MapDefaultControllerRoute:

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

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 slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
    .AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
    {
        options.PermitLimit = myOptions.SlidingPermitLimit;
        options.Window = TimeSpan.FromSeconds(myOptions.Window);
        options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = myOptions.QueueLimit;
    }));

var app = builder.Build();

app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages();
app.MapDefaultControllerRoute();  // RequireRateLimiting not called

app.Run();

Pertimbangkan pengontrol berikut:

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
    private readonly ILogger<Home2Controller> _logger;

    public Home2Controller(ILogger<Home2Controller> logger)
    {
        _logger = logger;
    }

    public ActionResult Index()
    {
        return View();
    }

    [EnableRateLimiting("sliding")]
    public ActionResult Privacy()
    {
        return View();
    }

    [DisableRateLimiting]
    public ActionResult NoLimit()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

Di pengontrol sebelumnya:

  • Pembatas tingkat kebijakan "fixed" diterapkan ke semua metode tindakan yang tidak memiliki atribut EnableRateLimiting dan DisableRateLimiting.
  • Pembatas tarif kebijakan "sliding" diterapkan pada tindakan Privacy.
  • Pembatasan laju dinonaktifkan pada NoLimit metode aksi.

Metrik pembatasan laju

Middleware pembatasan laju menyediakan metrik bawaan dan kemampuan pemantauan untuk membantu memahami bagaimana batas laju memengaruhi performa aplikasi dan pengalaman pengguna. Lihat Microsoft.AspNetCore.RateLimiting untuk daftar metrik.

Menguji titik akhir dengan pembatasan kecepatan

Sebelum menyebarkan aplikasi ke dalam produksi menggunakan pembatas laju, lakukan uji tekanan pada aplikasi untuk memvalidasi pembatas laju dan opsi yang digunakan. Misalnya, buat skrip JMeter dengan alat seperti BlazeMeter atau Apache JMeter HTTP(S) Test Script Recorder dan muat skrip ke Azure Load Testing.

Membuat partisi dengan input pengguna membuat aplikasi rentan terhadap Serangan Denial of Service (DoS). Misalnya, membuat partisi pada alamat IP klien membuat aplikasi rentan terhadap Serangan Penolakan Layanan dengan menggunakan spoofing alamat sumber IP. Untuk informasi selengkapnya, lihat Pemfilteran Masuk Jaringan BCP 38 RFC 2827: Mengalahkan Serangan Penolakan Layanan yang menggunakan pemalsuan alamat sumber IP.

Sumber Daya Tambahan: