Repository navigation
Router: preflight берёт внешний CorsMiddleware, основной запрос — внутренний #718
Description
Исправлено 14.09. В первой версии предлагалось брать для preflight последний
CorsMiddlewareстека. Это неверно: при таком решении ломается preflight для origin, разрешённого только внешней группой. Разбор — в ревью #727. Также неверным было утверждение, что до #708 расхождения не было. Разделы ниже обновлены.
Проблема
После #708 Router::dispatchOptionsPreflight() отвечает на OPTIONS через resolveFirstCorsMiddleware() — первый CorsMiddleware в стеке маршрута. Стек собирается как «внешние группы, затем внутренние, затем middleware маршрута»: в group() — array_merge($previousMiddlewares, $middlewares), в addRoute() — array_merge($this->currentMiddlewares, $middlewares). Первым оказывается самый внешний.
Для обычного запроса отрабатывают все middleware по порядку. CorsMiddleware::applyOriginHeadersIfAllowed() ставит заголовки, только если origin разрешён этому экземпляру, и ничего не снимает. Поэтому заголовки основного запроса — это накопленный результат всех CorsMiddleware стека, а не одного из них.
Preflight вызывает один экземпляр, и если у вложенной группы или маршрута свой CorsMiddleware с другими allowed_origins, preflight и основной запрос отвечают по-разному. Пример: внешняя группа /api/v1 пускает https://outer.example, вложенная /nested — https://inner.example.
| Origin | GET | Preflight: первый экземпляр (beta) |
Preflight: последний экземпляр |
|---|---|---|---|
| только outer | заголовки outer | заголовки outer | без заголовков |
| только inner | заголовки inner | без заголовков | заголовки inner |
| никому | без заголовков | без заголовков | без заголовков |
Выбор любого одного экземпляра оставляет расхождение, только на разных origin.
Отказ в сторону запрета — дыры нет. В текущих маршрутах CorsMiddleware один, на /api/v1 в config/routes/web.php, так что проявится это у дополнений с вложенными группами.
Расхождение было и до #708: внешний CorsMiddleware на OPTIONS возвращал ответ и останавливал стек, внутренний не вызывался.
Что сделать
На preflight вызывать все CorsMiddleware маршрута в порядке стека и отдавать ответ последнего. TokenMiddleware, RateLimitMiddleware и handler по-прежнему не вызывать (#706).
Критерии приёмки
- Preflight вызывает все
CorsMiddlewareстека маршрута в порядке стека, остальные middleware и handler — нет - Тест сравнивает фактические CORS-заголовки preflight и основного запроса для origin «только outer», «только inner», «оба уровня», «никому»; у уровней разные
allow_credentialsиmax_age
Контекст
- [Bug] Web API: CORS preflight OPTIONS возвращает 405 до CorsMiddleware #634 / fix(web-api): CORS preflight OPTIONS до CorsMiddleware #637 — preflight минтил токен
- Web API: отвечать на CORS preflight в Router, не входя в стек middleware #706 / Web API: CORS preflight вне стека middleware #708 — preflight вне стека middleware
- Router: preflight берёт внутренний CorsMiddleware #727 — первая попытка исправления, ревью
Activity