【问题标题】:Load a listener before the security or custom provider在安全或自定义提供程序之前加载侦听器
【发布时间】:2013-01-31 07:55:27
【问题描述】:

我正在开发一个与经典用户表单完全一样的自定义提供程序,但是我必须提供第二个参数来识别用户:websiteId(我正在创建一个动态网站平台)。

所以用户名不再是唯一的,而是用户名和 websiteId 的组合。

我成功创建了我的自定义身份验证,我遇到的最后一个问题是通过侦听器从域中获取 websiteId,它可以工作,但幸运的是,从域获取网站 id 的方法是在我的身份验证提供程序之后加载的,所以我无法及时获得 websiteId :(

我尝试更改侦听器优先级(测试 9999、1024、255 和 0,以及负数 -9999、-1024、-255 等...),但徒劳无功,总是在之后加载。

这是我的代码:

services.yml:

services:

    # Listeners _________________

    website_listener:
        class: Sybio\Bundle\WebsiteBundle\Services\Listener\WebsiteListener
        arguments: 
            - @doctrine
            - @sybio.website_manager
            - @translator
            - %sybio.states%
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onDomainParse, priority: 255 }

    # Security _________________

    sybio_website.user_provider:
        class: Sybio\Bundle\WebsiteBundle\Security\Authentication\Provider\WebsiteUserProvider
        arguments: [@website_listener, @doctrine.orm.entity_manager]

我的听众是“website_listener”,你可以看到我将它用于我的 sybio_website.user_provider 作为参数。

网站监听器:

// ...

class WebsiteListener extends Controller
{
    protected $doctrine;

    protected $websiteManager;

    protected $translator;

    protected $websiteId;

    /**
     * @var array
     */
    protected $entityStates;

    public function __construct($doctrine, $websiteManager, $translator, $entityStates)
    {
        $this->doctrine = $doctrine;
        $this->websiteManager = $websiteManager;
        $this->translator = $translator;
        $this->entityStates = $entityStates;
    }

    /**
     * @param Event $event
     */
    public function onDomainParse(Event $event)
    {
        $request = $event->getRequest();

        $website = $this->websiteManager->findOne(array(
            'domain' => $request->getHost(),
            'state' => $this->entityStates['website']['activated'],
        ));

        if (!$website) {
            throw $this->createNotFoundException($this->translator->trans('page.not.found'));
        }

        $this->websiteId = $website->getId();
    }

    /**
     * @param integer $websiteId
     */
    public function getWebsiteId()
    {
        return $this->websiteId;
    }
}

$websiteId 已补水,不像您在我的提供者中看到的那样及时...

网站用户提供者:

<?php
namespace Sybio\Bundle\WebsiteBundle\Security\Authentication\Provider;

// ...

class WebsiteUserProvider implements UserProviderInterface
{
    private $em;
    private $websiteId;
    private $userEntity;

    public function __construct($websiteListener, EntityManager $em)
    {
        $this->em = $em;
        $this->websiteId = $websiteListener->getWebsiteId(); // Try to get the website id from my listener, but it's method onDomainParse is not called in time
        $this->userEntity = 'Sybio\Bundle\CoreBundle\Entity\User';
    }

    public function loadUserByUsername($username)
    {
        // I need the websiteId here to identify the user by its username and the website:
        if ($user = $this->findUserBy(array('username' => $username, 'website' => $this->websiteId))) {
            return $user;
        }

        throw new UsernameNotFoundException(sprintf('No record found for user %s', $username));
    }

    // ...
}

所以任何想法都会受到赞赏;) 我花了很多时间设置我的身份验证配置,但现在我无法及时获取 websiteId,太糟糕了:(

感谢您的回答!

编辑:

我还需要了解我的身份验证系统的其他文件,我认为我无法在加载时控制提供程序的位置,因为它们在 security.yml 配置中被写入:

WebsiteAuthenticationProvider:

// ...

class WebsiteAuthenticationProvider extends UserAuthenticationProvider
{
    private $encoderFactory;
    private $userProvider;

    /**
     * @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider
     * @param UserCheckerInterface $userChecker
     * @param $providerKey
     * @param EncoderFactoryInterface $encoderFactory
     * @param bool $hideUserNotFoundExceptions
     */
    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
    {
        parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
        $this->encoderFactory   = $encoderFactory;
        $this->userProvider     = $userProvider;
    }

    /**
     * {@inheritdoc}
     */
    protected function retrieveUser($username, UsernamePasswordToken $token)
    {
        $user = $token->getUser();
        if ($user instanceof UserInterface) {
            return $user;
        }

        try {
            $user = $this->userProvider->loadUserByUsername($username);

            if (!$user instanceof UserInterface) {
                throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
            }

            return $user;
        } catch (UsernameNotFoundException $notFound) {
            throw $notFound;
        } catch (\Exception $repositoryProblem) {
            throw new AuthenticationServiceException($repositoryProblem->getMessage(), $token, 0, $repositoryProblem);
        }
    }

    // ...
}

工厂:

// ...

class WebsiteFactory extends FormLoginFactory
{
    public function getKey()
    {
        return 'website_form_login';
    }

    protected function getListenerId()
    {
        return 'security.authentication.listener.form';
    }

    protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
    {
        $provider = 'security.authentication_provider.sybio_website.'.$id;
        $container
            ->setDefinition($provider, new DefinitionDecorator('security.authentication_provider.sybio_website'))
            ->replaceArgument(0, new Reference($userProviderId))
            ->replaceArgument(2, $id)
        ;

        return $provider;
    }
}

SybioWebsiteBundle(依赖):

// ...

class SybioWebsiteBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new WebsiteFactory());
    }
}

安全性:

security:
    firewalls:
        main:
            provider: website_provider
            pattern:    ^/
            anonymous: ~
            website_form_login:
                login_path:  /login.html
                check_path:  /login
            logout:
                path:   /logout.html
                target: /

    providers:
        website_provider:
            id: sybio_website.user_provider

【问题讨论】:

  • 向你的监听器添加一个记录器,并让它在事件处理器被调用时生成调试消息,然后检查日志文件以查看它适合的位置。我怀疑还有其他事情发生。会话侦听器的优先级为 128。使用 120 的优先级可能是您想要的。
  • 是否尝试了负优先级?事件调度器,使用 ksort 对侦听器进行排序
  • 是的,我尝试用负数更改优先级...不起作用,我刚刚编辑了我的问题,您将看到我的提供程序系统,它们是通过工厂加载的,本身在听众之前加载,有没有办法改变这个?或者通过不使用监听器的干净解决方案获取网站 ID 的另一种方法?

标签: authentication symfony listener provider


【解决方案1】:

Firewall::onKernelRequest 注册优先级为 8 (sf2.2)。优先级 9 应确保首先调用您的侦听器(对我有用)。

我遇到了类似的问题,即在单个 sf2.2 应用程序中创建特定于子域​​的“活动”网站: {campaign}.{domain} 。每个用户都有许多活动,我和你一样,想阻止没有给定活动的用户登录。

我的解决方案是创建一个 Doctrine 过滤器,将我的广告系列标准添加到在 {campaign}.{domain} 下进行的每个相关查询中。 kernel.request 侦听器(优先级为 9!)负责在我的通用用户提供程序尝试 loadUserByUsername 之前激活过滤器。我使用 mongodb,但 ORM 的想法是相似的。

最好的部分是我仍在使用股票身份验证类。这基本上就是它的全部内容:

config.yml:

doctrine_mongodb:
    document_managers:
        default:
            filters:
                campaign:
                    class: My\Filter\CampaignFilter
                    enabled: false

CampaignFilter.php:

class CampaignFilter extends BsonFilter
{
    public function addFilterCriteria(ClassMetadata $targetMetadata)
    {
        $class = $targetMetadata->name;
        $campaign = $this->parameters['campaign'];
        $campaign = $campaign instanceof Campaign ? $campaign->getId() : $campaign;
        if ($targetMetadata->hasField('campaign')) {
            return array('campaign' => $this->parameters['campaign']);
        }
        if ($targetMetadata->hasField('campaigns')) {
            return array('campaigns' => $this->parameters['campaign']);
        }
        return array();
    }
}

我的监听器声明为:

    <service id="my.campaign_listener" class="My\EventListener\CampaignListener">
        <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="9" />
        <argument type="service" id="doctrine.odm.mongodb.document_manager" />
    </service>

监听类:

class CampaignListener
{
    private $dm;

    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
            return;
        }

        $request = $event->getRequest();
        if ($campaign = $request->attributes->get('campaign', false)) {
            $filters = $this->dm->getFilterCollection();
            $filter = $filters->enable('campaign');
            $filter->setParameter('campaign', $campaign);
        }
    }
}

由于我的路由配置,此处的请求中提供了“campaign”:

campaign:
    resource: "@My/Controller/CampaignController.php"
    type:     annotation
    host: "{campaign}.{domain}"
    defaults:
        campaign: test
        domain: %domain%
    requirements:
        domain: %domain%

.. %domain% 是来自 config.yml 或 config_dev.yml 的参数

【讨论】:

    【解决方案2】:

    就像 benki07 提供的响应一样,这是一个优先权问题,您必须将侦听器放在 Firewall::onKernelRequest 之前

    然后,您的监听器将被调用 -> 防火墙被调用,并且您的身份验证监听器被调用并注册了 webSiteId。

    【讨论】:

    • 我同意你的观点,但我不知道如何在我的防火墙之前加载我的监听器,你知道吗?缺少文档,我试图改变我的监听器的优先级是徒劳的,该方法总是在之后调用:s 有没有办法将我的监听器附加到“防火墙事件”上?谢谢!
    【解决方案3】:

    正如您在 SecurityExtension.php 中看到的那样,所使用的工厂没有任何优先级系统。它只是将您的工厂添加到数组的末尾,就是这样。 因此,不可能将您的自定义身份验证放在 symfony 的安全组件之前。

    一个选项可能是用你的类覆盖 DaoAuthenticationProvider 类参数。我希望 symfony2 将从工厂更改为注册表,您可以在其中添加带有标签和优先级的自定义身份验证,因为这对我来说还不够开放/关闭。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-09
      • 1970-01-01
      • 2021-09-23
      相关资源
      最近更新 更多