我们遇到了类似的问题。
- 我们希望能够访问错误页面中的身份验证令牌。
- 在网站的一部分位于防火墙后面的情况下,比如
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_path 到 true,则无需进行此特定修复。
编辑:
为了让 Symfony 使用我的路由器和异常监听器版本,我在 Kernel.php 的 process() 方法中添加了以下代码:
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')]);
// ...
}