Пользовательский шаблон ошибки 404 в ветке 2.5 с Symfony 4.1 - PullRequest
0 голосов
/ 26 сентября 2018

Я создал таможенные шаблоны Twig для отображения ошибок http, чтобы сохранить унифицированность дизайна сайта за счет расширения моей базовой разметки.(Я хочу сохранить свое меню навигации и отображать ошибку, в отличие от обычных сообщений об ошибках)

Работает, как и ожидалось, но для 404.

В меню навигации моего базового макета яесть много is_granted('SOME_ROLES') для отображения доступных разделов сайта в зависимости от прав пользователя.Когда выдается 404, меню навигации отображается так, как будто пользователь отключен: {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} ложно.

После некоторых поисков я обнаружил, что маршрутизатор выполняется перед брандмауэром.Поскольку при отбрасывании 404 не найдено ни одного маршрута, брандмауэр не запускается и права на шаблон не отправляются.

Единственный найденный мной обходной путь ( источник из 2014 )заключается в том, чтобы добавить в самом низу файлаways.yaml это определение маршрута:

pageNotFound:
    path: /{path}
    defaults:
        _controller: App\Exception\PageNotFound::pageNotFound

Поскольку все остальные маршруты не совпадают, этот должен быть не найден.

контроллер:

<?php

namespace App\Exception;

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class PageNotFound
{
    public function pageNotFound()
    {
        return (new NotFoundHttpException());
    }
}

Поскольку выполняется контроллер, брандмауэр выполняется, и страница ошибки 404 отображается, как я ожидал (ура!).

Мой вопрос: есть ли правильный способ исправить эту проблему вместо этого обходного пути?

1 Ответ

0 голосов
/ 27 сентября 2018

У нас была похожая проблема.

  • Мы хотели получить доступ к токену аутентификации на страницах ошибок.
  • В сценарии, когда часть веб-сайта находится за брандмауэром,скажем 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, тогда это конкретное исправление не требуется.


EDIT:

Чтобы Symfony использовал мои версии прослушивателя Router и Exception, я добавил следующий код в методе 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')]);

    // ...
}
...