Doc Gateway - PlutoGr/TravelAgency GitHub Wiki
TravelAgency.Gateway (src/Gateway/) это единая точка входа в API. Он не хранит данных и не содержит бизнес-логики: принимает запрос, проверяет вход и роль, ограничивает частоту и отправляет запрос в нужный сервис через YARP.
Браузер: POST /api/v1/bookings Cookie: access_token=eyJ…
Gateway: cookie → Authorization: Bearer eyJ…
маршрут bookings-route, политика RequireAuthenticated: роль Client, проходит
убрать /api/v1 → POST http://booking-service:8080/bookings
Booking: 201 Created { "id": "…", "status": 0, … }
Без cookie тот же запрос получит 401 прямо на Gateway и до Booking не дойдёт.
Порядок задан в Program.cs и важен: каждый шаг опирается на предыдущие.
| № | Шаг | Что делает |
|---|---|---|
| 1 | Forwarded headers | берёт реальный IP и схему из X-Forwarded-For, X-Forwarded-Proto, но только от адресов частных сетей (nginx в Docker) |
| 2 | Заголовки безопасности |
X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer, Content-Security-Policy: default-src 'self' и др. |
| 3 | Логирование запроса | Serilog |
| 4 | CorrelationIdMiddleware |
читает или создаёт X-Correlation-Id, кладёт его в логи и в ответ |
| 5 | GlobalExceptionHandlerMiddleware |
любое необработанное исключение превращается в 500 ProblemDetails |
| 6 | CORS | политика GatewayPolicy
|
| 7 | Лимиты запросов | 429 при превышении |
| 8 | CookieToAuthHeaderMiddleware |
cookie access_token → заголовок Authorization: Bearer; для хаба чата также параметр access_token в строке запроса |
| 9 | Authentication, Authorization | проверка JWT и политики маршрута |
| 10 | AuthRequestMiddleware |
для пустого POST /auth/refresh и /auth/logout подставляет тело { refreshToken } из cookie |
| 11 | Health checks |
/health/live, /health/ready
|
| 12 | Заглушка presign и YARP |
/api/v1/media/presign отвечает 404; остальное проксируется |
Ответы маршрута auth дополнительно проходят через AuthResponseTransformProvider. На login, register и refresh токены из ответа Identity переносятся в cookie, клиент получает { "success": true }. Gateway проверяет ответ Identity: если в ответе 200 или 201 на этих путях нет обоих токенов и сроков в будущем, или токен пришёл в 2xx на другом пути маршрута auth, клиент получает 502 без Set-Cookie, и тело Identity в JS не уходит. Подробно: Вход и сессия.
Полная таблица маршрутов, методов и прав: Публичный API. Коротко:
- 11 маршрутов YARP в
appsettings.json(секцияReverseProxy:Routes). Политика доступа задаётся полемAuthorizationPolicyу каждого маршрута. - При пересечении путей решает
Order:catalog-wizard-route(-1) раньше публичного GET каталога, поэтому гость на/catalog/manage/**получает 401;chat-hub-route(0) раньшеchat-route(1); маршрутыmedia/filesиmedia/manage(0) раньше общегоmedia-route(100). - Преобразование пути: у большинства маршрутов убирается
/api/v1; уauth-routeубирается/api/v1/authи добавляется/identity; уchat-hub-routeубирается/api/v1/chat. - Кластеры:
identity-cluster,catalog-cluster,booking-cluster,chat-cluster,media-cluster. В Docker адресаhttp://<имя>-service:8080, в Developmenthttp://localhost:5010...5050.
| Политика | Роли |
|---|---|
Anonymous |
без входа |
RequireAuthenticated |
Client, Manager, Admin |
RequireManager |
Manager, Admin |
RequireManagerOrAdmin |
Manager, Admin |
RequireAdmin |
Admin |
Имена политик и ролей общие для всех сервисов (TravelAgency.Shared.Contracts). Gateway проверяет только роль; права на конкретный объект (своя заявка, свой тур) проверяет сервис.
Проверяются подпись (ключ JwtSettings__SigningKey, не короче 32 символов), Issuer = TravelAgency.Identity, Audience = TravelAgency и срок жизни. Без ключа Gateway не запускается.
Встроенный RateLimiter ASP.NET Core с глобальным разбиением по IP клиента:
| Группа | Лимит | Окно |
|---|---|---|
login, register, refresh, logout
|
5 запросов (RateLimiting:Auth:Limit) |
1 секунда |
| всё остальное | 200 запросов (RateLimiting:Global:Limit) |
1 минута |
Если задана строка ConnectionStrings__Redis (на dev задана), счётчики хранятся в Redis (RedisFixedWindowRateLimiter) и общие для всех экземпляров Gateway. Если Redis недоступен и RateLimiting:FailOpen = true (по умолчанию), запросы пропускаются. Без Redis счётчики в памяти процесса.
Лимит на вход задаётся только RateLimiting:Auth:Limit (по умолчанию 5) и RateLimiting:Auth:PeriodSeconds (по умолчанию 1). В appsettings.json явно заданы 5 запросов и окно в 1 секунду, поэтому так же работает dev и локальный Docker. Шестой POST /api/v1/auth/login в этом окне получает 429.
Фронтенд при 429 показывает под формой входа «Слишком много попыток входа. Подождите немного и попробуйте снова.». Введённая почта остаётся, пароль очищается, кнопка «Войти» снова доступна, следующая попытка уходит обычным запросом. Неверный пароль и недоступность сервиса показываются как раньше. Сужение доверенных прокси для X-Forwarded-For (сейчас доверены все частные сети): задача #32.
Разрешённые origin задаются Cors:AllowedOrigins. На сервере это https://<SITE_DOMAIN> (через Cors__AllowedOrigins__0 в compose), в Development http://localhost:3000 и http://localhost:5173. Значение по умолчанию в appsettings.json заглушка и на dev переопределяется. Разрешены методы GET, POST, PUT, PATCH, DELETE, OPTIONS, заголовки Authorization, Content-Type, Accept, X-Correlation-Id, X-Request-ID, Cache-Control, и передача cookie.
-
GET /health/live: процесс жив, зависимости не проверяются. -
GET /health/ready: Gateway запрашивает/health/liveкаждого сервиса (таймаут 5 секунд). ОтветHealthy, если все ответили 2xx, иначеDegraded. Снаружи этот ответ виден как/healthzна nginx.
| Секция | Что задаёт |
|---|---|
ReverseProxy |
маршруты и кластеры YARP |
AuthRoute |
пути login, register, refresh, logout для cookie-преобразований |
Cookie |
имена cookie, Secure, SameSite=Lax, Path=/, необязательный Domain
|
JwtSettings |
Issuer, Audience, ключ подписи (ключ только из окружения) |
RateLimiting |
лимиты и FailOpen
|
Cors |
разрешённые origin, методы, заголовки |
HealthCheckEndpoints |
адреса /health/live сервисов |
Otel |
адрес OTLP-экспорта трассировки |
ConnectionStrings:Redis |
Redis для лимитов (необязательно) |
Serilog с {CorrelationId} в шаблоне строки, OpenTelemetry (инструментация ASP.NET Core и HttpClient), сервис регистрируется как TravelAgency.Gateway.
См. также: Архитектура · Публичный API