TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.74K subscribers
Post #3317 1.38K
День 2772. #BestPractices
Лучшие Практики Безопасности REST API в
ASP.NET Core. Продолжение
1-3
4-7

8. Типы контента и размер запроса
Конечная точка, принимающая JSON, должна отклонять другие типы контента, и ни одна конечная точка не должна принимать неограниченное тело запроса. Загрузка нескольких гигабайтных файлов — простой способ исчерпать память сервера и вывести из строя ваш API. Ограничьте тип контента с помощью [Consumes] и размер тела запроса с помощью [RequestSizeLimit]:
[HttpPost]
[Consumes("application/json")]
[RequestSizeLimit(1_000_000)] // 1 MB
public IActionResult Create(CreateProductRequest request)
{
// …
}

Также можно задать лимит глобально в Kestrel:
builder.WebHost.ConfigureKestrel(opts =>
{
opts.Limits.MaxRequestBodySize = 1_000_000;
});

То же относится и к объёму данных, которые клиент может получить. Всегда используйте пагинацию с ограничением максимального размера страницы в конечных точках коллекций, чтобы клиент не мог запросить неограниченный набор результатов:
[HttpGet]
public IActionResult GetProducts(
int page = 1, int pageSize = 20)
{
// Принудительно ограничиваем размер страницы
pageSize = Math.Min(pageSize, 100);

// …
}


9. Параметризованные запросы и EF Core
Никогда не создавайте SQL-запросы путем конкатенации строк из пользовательского ввода — это способ внедрения SQL-инъекций. Параметризованные запросы сохраняют пользовательский ввод как данные, а не как исполняемый SQL. EF Core параметризует всё по умолчанию, поэтому LINQ-запрос всегда безопасен:
// EF Core параметризует 'search'
var products = await _dbContext.Products
.Where(p => p.Name.Contains(search))
.ToListAsync();

Когда вам нужен чистый SQL, используйте FromSql или FromSqlInterpolated, которые преобразуют интерполированные значения в параметры, а не в литеральный текст:

// 'category' становится параметром SQL
var products = await _dbContext.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();

// Никогда не делайте так
var sql = "SELECT * FROM Products WHERE Category = '" + category + "'";

// Не используйте FromSqlRaw с интерполяцией
var products = await _dbContext.Products
.FromSqlRaw(
$"SELECT * FROM Products WHERE Category = {category}")
.ToListAsync();


10. Лимитирование трафика
Без ограничений один клиент — или злоумышленник — может атаковать точку входа методом перебора паролей или завалить API запросами до тех пор, пока он не выйдет из строя. ASP.NET Core имеет встроенное лимитирование запросов, которое настраивается в Program.cs:
builder.Services.AddRateLimiter(opts =>
{
opts.AddFixedWindowLimiter("api", lim =>
{
lim.PermitLimit = 10;
lim.Window = TimeSpan.FromMinutes(1);
lim.QueueLimit = 0;
});

opts.RejectionStatusCode =
StatusCodes.Status429TooManyRequests;
});

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

Затем можно применить политику к методу действия контроллера:
[HttpPost("login")]
[EnableRateLimiting("api")]
public IActionResult Login(LoginRequest request)
{ /* … */ }

или конечной точке минимальных API:
app.MapPost("/login", (LoginRequest request) => 
{ /* … */ })
.RequireRateLimiting("api");

Когда клиент превышает лимит, он получает ошибку 429 Too Many Requests. Это снижает вероятность атак методом перебора паролей и атак типа «отказ в обслуживании».

11. CORS
CORS (Cross-Origin Resource Sharing) контролирует, какие веб-источники могут отправлять запросы к вашему API. Опасно использовать AllowAnyOrigin(), что позволяет любому веб-сайту в интернете вызывать ваш API от имени авторизованного пользователя. Определите именованную политику, которая точно перечисляет разрешённые источники, методы и заголовки:
builder.Services.AddCors(opts =>
{
opts.AddPolicy("Web", p =>
p.WithOrigins("https://mywebsite.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type"));
});

var app = builder.Build();
app.UseCors("Web");

Так только https://mywebsite.com может вызвать ваш API, только с этими методами и с такими заголовками. Всё остальное отклоняется. Оставьте AllowAnyOrigin для действительно общедоступных, неаутентифицированных API — и никогда не используйте его в сочетании с учётными данными.

12. Минимальные сведения об ошибке
Ответ об ошибке должен помогать вызывающей стороне, а не злоумышленнику. Обычная страница исключения раскрывает трассировку стека, версии фреймворков, пути к файлам и SQL-запросы — карту ваших внутренних механизмов. Вместо этого возвращайте чистое, стандартное сообщение об ошибке, используя сведения о проблеме:
builder.Services.AddProblemDetails();

var app = builder.Build();

if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler();

В производственной среде необработанное исключение возвращает структурированное тело ProblemDetails с кодом состояния и стандартным сообщением — и ничего о ваших внутренних процессах.

Окончание следует…

Источник:
https://antondevtips.com/blog/rest-api-security-best-practices-in-aspnetcore
  • 👍 10
More from @netdeveloperdiary
  1. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  2. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  3. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  4. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  5. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 21, 2026🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →