带有symfony 4.1的Twig 2.5中的自定义404错误模板

时间:2018-09-26 12:36:54

标签: php twig http-status-code-404 symfony4 symfony-routing

我创建了用于http错误显示的Custom Twig模板,以通过扩展基本布局来使网站设计统一。 (与常规错误消息不同,我想保留导航菜单并显示错误)

它可以正常工作,但适用于404。

在基本布局的导航菜单中,根据用户的权限,我有很多is_granted('SOME_ROLES')用于显示网站的可用部分。抛出404时,导航菜单显示为好像用户断开连接:{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}为假。

经过一些搜索,我发现路由器是在防火墙之前执行的。由于在抛出404时找不到路由,因此不会执行防火墙,也不会将权限发送到模板。

我发现(source from 2014)的唯一解决方法是在route.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 :(得分:1)

我们遇到了类似的问题。

  • 我们希望能够访问错误页面中的身份验证令牌。
  • 在网站的某些部分位于防火墙后面的情况下,例如example.com/supersecretarea/,我们希望未经授权的用户访问example.com/supersecretarea/之后的任何URL时都得到403错误代码,即使在页面不存在的事件。 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_pathtrue,则不需要此特定修复程序。


编辑:

要使Symfony使用我的路由器和异常侦听器版本,我在process()的{​​{1}}方法中添加了以下代码:

Kernel.php