简体   繁体   English

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

[英]Custom 404 error template in twig 2.5 with symfony 4.1

I created customs Twig templates for http error display to keep the site design unified by extending my base layout.我创建了用于 http 错误显示的自定义 Twig 模板,通过扩展我的基本布局来保持站点设计的统一。 (I want to keep my navigation menu and display the error, unlike the regular error messages) (与常规错误消息不同,我想保留我的导航菜单并显示错误)

It's working as expected but for the 404.它按预期工作,但对于 404。

In the navigation menu of my base layout, I have a lot of is_granted('SOME_ROLES') to display the availables sections of the site depending of user's rights.在我的基本布局的导航菜单中,我有很多is_granted('SOME_ROLES')来根据用户的权限显示站点的可用部分。 When a 404 is thrown, the navigation menu is displayed as if the user is disconnected : {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} being false.当抛出 404 时,导航菜单显示为好像用户已断开连接: {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} false。

After some searches, I found that the router is executed before the firewall.经过一番搜索,我发现路由器是在防火墙之前执行的。 Since no route is found when a 404 is thrown, the firewall isn't executed and the rights aren't send to the template.由于在抛出 404 时找不到路由,因此不会执行防火墙并且不会将权限发送到模板。

The only workaround I found ( source from 2014 ) is to add at the very bottom of the routes.yaml file this route definition :我发现的唯一解决方法( 来自 2014 年)是在 routes.yaml 文件的最底部添加此路由定义:

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

Since every other routes hasn't match, this one should be the not found.由于其他所有路线都没有匹配,因此应该找不到这条路线。

The controller :控制器:

<?php

namespace App\Exception;

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

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

Because a controller is executed, the firewall is executed and the 404 error page is shown as I expected (hooray !).因为执行了控制器,所以执行了防火墙并且 404 错误页面如我预期的那样显示(万岁!)。

My question is : Is there any proper way to fix that issue instead of that workaround?我的问题是:是否有任何正确的方法来解决该问题而不是该解决方法?

We had a similar issue.我们有一个类似的问题。

  • We wanted to have access to an authentication token in error pages.我们希望能够访问错误页面中的身份验证令牌。
  • In the scenario where part of the website is behind a firewall, say example.com/supersecretarea/ , we wanted than unauthorized users get a 403 error code when accessing any url behind example.com/supersecretarea/ , even in the event that the page doesn't exist .在网站的一部分位于防火墙后面的情况下,比如example.com/supersecretarea/ ,我们希望未经授权的用户在访问example.com/supersecretarea/后面的任何 url 时得到 403 错误代码,即使页面不存在 Symfony's behavior does not allow that and checks for a 404 (either because there is no route or because the route has parameter which didn't resolve, like example.com/supersecretarea/user/198 when the is no user 198 ). Symfony 的行为不允许这样做并检查 404(因为没有路由或因为路由具有未解析的参数,例如example.com/supersecretarea/user/198当没有用户198 )。

What we ended up doing was to override the default router in Symfony ( Symfony\\Bundle\\FrameworkBundle\\Routing\\Router ) to modify its behavior:我们最终做的是覆盖 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 simply renders the 404 error page: CatchAllController只是呈现 404 错误页面:

public function catchAll(): Response
{
    return new Response(
        $this->templating->render('bundles/TwigBundle/Exception/error404.html.twig'),
        Response::HTTP_NOT_FOUND
    );
}

What happens is that during the regular process of Symfony's router, if something should trigger a 404 error, we catch that exception within the matchRequest function.发生的情况是,在 Symfony 路由器的常规过程中,如果某些事情应该触发 404 错误,我们会在matchRequest函数中捕获该异常。 This function is supposed to return information about which controller action to run to render the page, so that's what we do: we tell the router that we want to render a 404 page (with a 404 code).这个函数应该返回有关运行哪个控制器操作来呈现页面的信息,所以这就是我们所做的:我们告诉路由器我们想要呈现一个 404 页面(带有 404 代码)。 All the security is handled in between matchRequest returning and catchAll being called, so firewalls get to trigger 403 errors, we have an authentication token, etc.所有安全性都在matchRequest返回和catchAll被调用之间处理,因此防火墙会触发 403 错误,我们有一个身份验证令牌等。


There is at least one functional issue to this approach (that we managed to fix for now).这种方法至少存在一个功能问题(我们现在设法解决了这个问题)。 Symfony has an optional system that remembers the last page you tried to load, so that if you get redirected to the login page and successfully log in, you'll be redirected to that page you were trying to load initially. Symfony 有一个可选系统,它会记住您尝试加载的最后一个页面,因此如果您被重定向到登录页面并成功登录,您将被重定向到您最初尝试加载的页面。 When the firewall throws an exception, this occurs:当防火墙抛出异常时,会发生这种情况:

// 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());
    }
}

But now that we allow non-existing pages to trigger firewall redirections to the login page (say, example.com/registered_users_only/* redirects to the loading page, and an unauthenticated user clicks on example.com/registered_users_only/page_that_does_not_exist ), we absolutely don't want to save that non-existing page as the new "TargetPath" to redirect to after a successful login, otherwise the user will see a seemingly random 404 error.但是现在我们允许不存在的页面触发防火墙重定向到登录页面(例如, example.com/registered_users_only/*重定向到加载页面,未经身份验证的用户点击example.com/registered_users_only/page_that_does_not_exist ),我们绝对不要将那个不存在的页面保存为新的“TargetPath”以在成功登录后重定向到,否则用户会看到一个看似随机的 404 错误。 We decided to extend the exception listener's setTargetPath , and defined a service that toggles whether a target path should be saved by the exception listener or not.我们决定扩展异常侦听器的setTargetPath ,并定义一个服务来切换目标路径是否应由异常侦听器保存。

// Our extended ExceptionListener
protected function setTargetPath(Request $request): void
{
    if ($this->targetPathSavingStatus->shouldSave()) {
        parent::setTargetPath($request);
    }
}

That's the purpose of the commented $this->targetPathSavingStatus->disableSaveTargetPath();这就是注释$this->targetPathSavingStatus->disableSaveTargetPath();的目的$this->targetPathSavingStatus->disableSaveTargetPath(); line from above: to turn the default-on status of whether to save target path on firewall exceptions to off when there's a 404 (the targetPathSavingStatus variables here point to a very simple service used only to store that piece of information).上面的行:在出现 404 时,将是否在防火墙异常上保存目标路径的默认打开状态关闭(此处的targetPathSavingStatus变量指向一个非常简单的服务,仅用于存储该信息)。

This part of the solution is not very satisfactory.这部分解决方案不是很令人满意。 I'd like to find something better.我想找到更好的东西。 It does seem to do the job for now though.不过,它现在似乎确实可以胜任。

Of course if you have always_use_default_target_path to true , then there is no need for this particular fix.当然,如果您将always_use_default_target_path设为true ,则无需进行此特定修复。


EDIT:编辑:

To make Symfony use my versions of the Router and Exception listener, I added the following code in the process() method of Kernel.php :为了让 Symfony 使用我的路由器和异常侦听器版本,我在Kernel.phpprocess()方法中添加了以下代码:

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')]);

    // ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM