2

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

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

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

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

这是我的代码:

服务.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 配置中是有的:

网站身份验证提供者:

// ...

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
4

3 回答 3

3

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

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

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

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

配置.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 的参数

于 2013-03-14T00:23:29.150 回答
0

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

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

于 2013-02-18T10:14:54.020 回答
0

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

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

于 2015-08-24T08:54:44.163 回答