Практикум — безопасность и устойчивость
Практикум, шаг 6 из 8. Закрепляем безопасный и масштабируемый API поверх catalog-api и orders-api. См. методы защиты данных.
Модель угроз (учебная)
| Актор | Доступ |
|---|---|
| Внешний клиент (браузер, Postman) | JWT → только orders-api и публичные GET каталога |
| orders-api | X-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-api | CATALOG_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.Timeout | 5 с |
| Повтор POST резерва | только с тем же Idempotency-Key |
| Ответ при недоступности каталога | 502 + Problem Details |
| Circuit breaker | Polly в C# после базового сценария |
Идемпотентность резерва уже в catalog-api: повтор сети после 201 не списывает остаток дважды.
Валидация и лимиты
- Pydantic / FluentValidation — отклонять лишние поля (
model_config = extra="forbid"). - Rate limiting —
slowapiв 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 и события.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Два сервиса OrderDesk: каталог на Python и заказы на C#, границы ответственности, потоки REST и WebSocket. Ресурсы OrderDesk, таблица методов HTTP, коды ответов и фрагмент OpenAPI для catalog-api и orders-api. Доменные сущности OrderDesk, DTO для REST, маппинг Python (Pydantic) и C# (record + ручной маппер). FastAPI, SQLite, эндпоинты товаров и резервирования, Pydantic и проверка через uvicorn. ASP.NET Core 8, Minimal API, HttpClient к catalog-api, SQLite и создание заказа с резервом. Протокол JSON-сообщений, hub в ASP.NET Core, heartbeat и подписка клиента на статусы OrderDesk. Коллекция Postman, переменные окружения и сквозной сценарий OrderDesk — товар, заказ, WebSocket.Практикум — сценарий и архитектура OrderDesk
Практикум — проектирование контракта API
Практикум — модели данных и маппинг DTO
Практикум — сервис каталога на Python
Практикум — сервис заказов на C#
Практикум — WebSocket и события заказов
Практикум — проверка в Postman