【问题标题】:How to get current firewall's check_path?如何获取当前防火墙的 check_path?
【发布时间】:2018-02-01 01:13:17
【问题描述】:

问题:如何通过给定的防火墙名称获取form_login.check_path

我们订阅Symfony\Component\Security\Http\SecurityEvent::INTERACTIVE_LOGIN 是为了在具有多个防火墙的应用程序中记录成功登录。

一个防火墙通过 Guard 身份验证使用 JWT 令牌,这具有负面影响,即对于具有有效令牌的每个请求都会触发此事件。

我们目前已通过手动检查当前路由是否与防火墙的检查路径匹配并停止事件传播以及提前返回来解决此问题。

随着我们添加更多防火墙(使用不同的令牌),我想更一般地解决这个问题。因此,我想检查当前路由是否与当前防火墙检查路径匹配而不硬编码任何路由或防火墙名称。

有一个类可以为 Twig logout_path() 方法使用的当前防火墙生成 注销 URL,该方法以某种方式从防火墙侦听器获取注销路由/路径。 (Symfony\Component\Security\Http\Logout\LogoutUrlGenerator)

在我进入一个漫长的调试会话之前,我想也许有人以前解决过这个案例;)

有什么想法吗?

示例代码:

class UserEventSubscriber implements EventSubscriberInterface
{

    /** @var LoggerInterface */
    protected $logger;

    /** @var FirewallMapInterface|FirewallMap */
    protected $firewallMap;

    public function __construct(LoggerInterface $logger, FirewallMapInterface $firewallMap)
    {
        $this->logger = $logger;
        $this->firewallMap = $firewallMap;
    }

    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $request = $event->getRequest();
        $firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
        $routeName = $request->get('_route');

        if (('firewall_jwt' === $firewallName) && ('firewall_jwt_login_check' !== $routeName)) {
            $event->stopPropagation();
            return;
        }

        $this->logger->info(
            'A User has logged in interactively.',
            array(
                'event' => SecurityEvents::INTERACTIVE_LOGIN,
                'user' => $event->getAuthenticationToken()->getUser()->getUuid(),
        ));

【问题讨论】:

  • 你使用的是 Symfony 版本 check_path 上,然后是 job is done! Documentation.
  • @yceruto 我知道 JSON 身份验证,但问题是登录 (form_login/json_login) 和身份验证过程本身是两个不同的过程。要使 JWT 令牌正常工作,您需要保护或自定义身份验证提供程序。触发SecurityEvents::INTERACTIVE_LOGIN 的防火墙甚至没有设置form_login 选项,但只要保护身份验证成功,仍会触发这些事件。我们正在使用第二个防火墙,它只允许匿名访问登录/注册/注销/密码重置路由。你明白吗?
  • 是的,我愿意。您可能需要手动执行此操作,即将此配置注入订阅者。在答案中看到一个想法:)

标签: symfony


【解决方案1】:

check_path 选项仅在身份验证工厂/侦听器中可用,因此您可以在构建容器时手动将此配置传递给订阅者类。

此解决方案考虑到check_path 可能是路由名称或路径,这就是HttpUtils 服务也被注入的原因:

namespace AppBundle\Subscriber;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;

class UserEventSubscriber implements EventSubscriberInterface
{
    private $logger;
    private $httpUtils;
    private $firewallMap;
    private $checkPathsPerFirewall;

    public function __construct(LoggerInterface $logger, HttpUtils $httpUtils, FirewallMapInterface $firewallMap, array $checkPathsPerFirewall)
    {
        $this->logger = $logger;
        $this->httpUtils = $httpUtils;
        $this->firewallMap = $firewallMap;
        $this->checkPathsPerFirewall = $checkPathsPerFirewall;
    }

    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $request = $event->getRequest();
        $firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
        $checkPath = $this->checkPathsPerFirewall[$firewallName];

        if (!$this->httpUtils->checkRequestPath($request, $checkPath)) {
            $event->stopPropagation();

            return;
        }

        $this->logger->info('A User has logged in interactively.', array(
            'event' => SecurityEvents::INTERACTIVE_LOGIN,
            'user' => $event->getAuthenticationToken()->getUser()->getUsername(),
        ));
    }

    public static function getSubscribedEvents()
    {
        return [SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'];
    }
}

将此订阅者注册为服务 (AppBundle\Subscriber\UserEventSubscriber) 后,我们需要在您的 DI 扩展中实现 PrependExtensionInterface,以便能够访问安全配置并使用每个防火墙的检查路径完成订阅者定义:

namespace AppBundle\DependencyInjection;

use AppBundle\Subscriber\UserEventSubscriber;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;

class AppExtension extends Extension implements PrependExtensionInterface
{
    // ...

    public function prepend(ContainerBuilder $container)
    {
        $checkPathsPerFirewall = [];

        $securityConfig = $container->getExtensionConfig('security');
        foreach ($securityConfig[0]['firewalls'] as $name => $config) {
            if (isset($config['security']) && false === $config['security']) {
                continue; // skip firewalls without security
            }

            $checkPathsPerFirewall[$name] = isset($config['form_login']['check_path'])
                ? $config['form_login']['check_path']
                : '/login_check'; // default one in Symfony
        }

        $subscriber = $container->getDefinition(UserEventSubscriber::class);
        $subscriber->setArgument(3, $checkPathsPerFirewall);
    }
}

我希望它符合您的需要。

【讨论】:

  • 嗯,我之前一直在考虑在 DI 扩展中获取配置……这对我来说实际上有点像一个肮脏的解决方案。我认为有一种更简单的方法可以直接获取路线/路径。来自 RequestMatcher / Security Listener。无论如何,我会给你的解决方案一个镜头,并很快回复你。感谢您帮助我!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-09
  • 2021-08-27
  • 2017-03-25
  • 1970-01-01
  • 2016-02-23
相关资源
最近更新 更多