У нас была аналогичная проблема.
- Мы хотели иметь доступ к токену аутентификации на страницах ошибок.
- В сценарии, где часть веб-сайта находится за брандмауэром, скажем,
example.com/supersecretarea/
, мы хотели, чтобы неавторизованные пользователи получали код ошибки 403 при доступе к любому URL-адресу за example.com/supersecretarea/
, даже если страница не существует. Поведение Symfony не позволяет этого и проверяет 404 (либо из-за отсутствия маршрута, либо из-за того, что параметр маршрута не разрешен, например example.com/supersecretarea/user/198
, когда нет пользователя 198
).
В итоге мы переопределили маршрутизатор по умолчанию в Symfony (Symfony\Bundle\FrameworkBundle\Routing\Router
), чтобы изменить его поведение:
public function matchRequest(Request $request): array
{
try {
return parent::matchRequest($request);
} catch (ResourceNotFoundException $e) {
// Ignore this next line for now
// $this->targetPathSavingStatus->disableSaveTargetPath();
return [
'_controller' => 'App\Controller\CatchAllController::catchAll',
'_route' => 'catch_all'
];
}
}
CatchAllController
просто отображает страницу ошибки 404:
public function catchAll(): Response
{
return new Response(
$this->templating->render('bundles/TwigBundle/Exception/error404.html.twig'),
Response::HTTP_NOT_FOUND
);
}
Что происходит, так это то, что во время обычного процесса маршрутизатора Symfony, если что-то должно вызвать ошибку 404, мы перехватываем это исключение в функции matchRequest
. Эта функция должна возвращать информацию о том, какое действие контроллера нужно выполнить для рендеринга страницы, вот что мы делаем: мы сообщаем маршрутизатору, что мы хотим рендерить страницу 404 (с кодом 404). Вся безопасность обрабатывается между возвратом matchRequest
и вызовом catchAll
, поэтому брандмауэры могут вызывать ошибки 403, у нас есть токен аутентификации и т. д.
В этом подходе есть по крайней мере одна функциональная проблема (которую нам удалось исправить на данный момент). Symfony имеет дополнительную систему, которая запоминает последнюю страницу, которую вы пытались загрузить, поэтому, если вы будете перенаправлены на страницу входа и успешно войдете в систему, вы будете перенаправлены на ту страницу, которую вы пытались загрузить изначально. Когда брандмауэр выдает исключение, происходит следующее:
// Symfony\Component\Security\Http\Firewall\ExceptionListener
protected function setTargetPath(Request $request)
{
// session isn't required when using HTTP basic authentication mechanism for example
if ($request->hasSession() && $request->isMethodSafe(false) && !$request->isXmlHttpRequest()) {
$this->saveTargetPath($request->getSession(), $this->providerKey, $request->getUri());
}
}
Но теперь, когда мы позволяем несуществующим страницам запускать перенаправления брандмауэра на страницу входа (скажем, example.com/registered_users_only/*
перенаправляет на страницу загрузки, а пользователь, не прошедший проверку подлинности, нажимает на example.com/registered_users_only/page_that_does_not_exist
), мы абсолютно не хотим сохранять эту несуществующую страницу как новый «TargetPath» для перенаправления после успешного входа в систему, иначе пользователь увидит, казалось бы, случайную ошибку 404. Мы решили расширить setTargetPath
прослушивателя исключений и определили службу, которая переключает, должен ли прослушиватель исключений сохранять целевой путь или нет.
// Our extended ExceptionListener
protected function setTargetPath(Request $request): void
{
if ($this->targetPathSavingStatus->shouldSave()) {
parent::setTargetPath($request);
}
}
Это цель закомментированной строки $this->targetPathSavingStatus->disableSaveTargetPath();
сверху: отключить состояние по умолчанию, чтобы сохранить целевой путь в исключениях брандмауэра, когда есть 404 (переменные targetPathSavingStatus
здесь указывают на очень простую службу, используемую только для хранения этой части информации).
Эта часть решения не очень удовлетворительна. Я хотел бы найти что-то лучше. Тем не менее, похоже, что на данный момент он выполняет свою работу.
Конечно, если у вас от always_use_default_target_path
до true
, то конкретно это исправление не нужно.
ИЗМЕНИТЬ:
Чтобы Symfony использовал мои версии Router и Exception listener, я добавил следующий код в метод process()
Kernel.php
:
public function process(ContainerBuilder $container)
{
// Use our own CatchAll router rather than the default one
$definition = $container->findDefinition('router.default');
$definition->setClass(CatchAllRouter::class);
// register the service that we use to alter the targetPath saving mechanic
$definition->addMethodCall('setTargetPathSavingStatus', [new Reference('App\Routing\TargetPathSavingStatus')]);
// Use our own ExceptionListener so that we can tell it not to use saveTargetPath
// after the CatchAll router intercepts a 404
$definition = $container->findDefinition('security.exception_listener');
$definition->setClass(FirewallExceptionListener::class);
// register the service that we use to alter the targetPath saving mechanic
$definition->addMethodCall('setTargetPathSavingStatus', [new Reference('App\Routing\TargetPathSavingStatus')]);
// ...
}
person
NicolasB
schedule
27.09.2018