Перейти к основному содержимому

Практикум — безопасность и устойчивость

Разработчику Архитектору

Практикум, шаг 6 из 8. Закрепляем безопасный и масштабируемый API поверх catalog-api и orders-api. См. методы защиты данных.


Модель угроз (учебная)

АкторДоступ
Внешний клиент (браузер, Postman)JWT → только orders-api и публичные GET каталога
orders-apiX-Api-Key → резервы в catalog-api
Злоумышленник в LANперехват без TLS, подбор ключей

В продакшене все URL — HTTPS (https://, wss://). Локально допустим http:// только в dev.


JWT для клиентов (orders-api)

Настройка в Program.cs:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var jwt = builder.Configuration.GetSection("Jwt");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwt["Issuer"],
ValidAudience = jwt["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwt["SigningKey"]!)),
};
// WebSocket передаёт токен в query — см. шаг 7
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(token))
context.Token = token;
return Task.CompletedTask;
}
};
});

Правила:

  • Короткий TTL (15–60 минут), refresh-токен — отдельный эндпоинт при необходимости.
  • Ключ подписи — из переменной окружения Jwt__SigningKey, не из git.
  • В JWT кладём sub (user id), роли role при RBAC.

API-ключ между сервисами

СервисПеременнаяЗаголовок
catalog-apiCATALOG_API_KEYвходящий X-Api-Key
orders-apiтот же секрет в Catalog:ApiKeyисходящий X-Api-Key

Ротация: два активных ключа в конфиге, старый отключают через неделю.

В FastAPI ключ сравнивайте через secrets.compare_digest, чтобы избежать timing attack:


import secrets

def require_api_key(x_api_key: str | None = Header(default=None)):
expected = os.environ.get("CATALOG_API_KEY", "dev-catalog-key-change-me")
if not x_api_key or not secrets.compare_digest(x_api_key, expected):
raise HTTPException(status_code=401, detail="Invalid API key")

Сквозная трассировка

Клиент генерирует X-Request-Id (UUID). orders-api пробрасывает его в HttpClient:

client.DefaultRequestHeaders.Add("X-Request-Id", requestId);

В FastAPI middleware логирует тот же id. В логах обоих сервисов один запрос склеивается за секунды.


Таймауты и отказ каталога

ПараметрРекомендация
HttpClient.Timeout5 с
Повтор POST резерватолько с тем же Idempotency-Key
Ответ при недоступности каталога502 + Problem Details
Circuit breakerPolly в C# после базового сценария

Идемпотентность резерва уже в catalog-api: повтор сети после 201 не списывает остаток дважды.


Валидация и лимиты

  • Pydantic / FluentValidation — отклонять лишние поля (model_config = extra="forbid").
  • Rate limitingslowapi в FastAPI, AspNetCoreRateLimit в C# (например 100 req/min на IP для /auth/token).
  • Максимальный размер телаMaxRequestBodySize в Kestrel, лимит строк в заказе (например 50 позиций).

CORS и WebSocket

Для браузера с другого origin:

builder.Services.AddCors(o => o.AddPolicy("Spa", p =>
p.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowCredentials()));

WebSocket с cookies или query-токеном — только с явным списком origin. В Postman CORS не мешает.


Чек-лист перед Postman

  • Секреты вынесены в env, не в репозиторий
  • POST /reservations без ключа → 401
  • POST /orders без JWT → 401
  • Остановлен catalog-api → заказ возвращает 502, а не 500 без текста

Сопоставление с интерактивной песочницей

На странице раздела включите «Catalog down» и создайте заказ — увидите 502, как в проде при падении зависимости. Сценарий «Идемпотентность» дублирует поведение Idempotency-Key из этой главы.


Чек-лист безопасности перед демо

  • Секреты только в env / user-secrets, в git — .env.example без значений
  • compare_digest / constant-time сравнение API-ключа (Python)
  • JWT validation: issuer, audience, lifetime, clock skew ≤ 2 мин
  • Логи без тел с паролями и без полного Authorization
  • Rate limit на /auth/token

Далее — WebSocket и события.

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").