dtdfj08626 2015-08-13 09:23 采纳率: 0%
浏览 85

Symfony2 - 安装了FOS用户软件包的自定义身份验证提供程序

I need help with building custom authentication in Symfony2 project. I've read the symfony cookbook http://symfony.com/doc/2.3/cookbook/security/custom_authentication_provider.html and found many questions about the custom authentication but they didn't answer to my question in situation when I trying to do this with FOS User Bundle. I spent many ours of investigation of symfony authentication process but can't understand where am I wrong.

So what I have now:

  1. FOS User Bundle successfully installed and configured following by the official documentation.
  2. Sonata User Bundle installed.
  3. Custom authentication listener, token, provider, factory added and configured. After that I normally log in but as I see my new authentication provider is not used and the user is logged in with FOS's "form_login".

Here is my code:

User entity class:

<?php
    namespace Acme\UserBundle\Entity;

    use Sonata\UserBundle\Entity\BaseUser as BaseUser;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
    use \Acme\BoardBundle\Entity\Card;

    /**
     * @ORM\Entity
     * @ORM\HasLifecycleCallbacks
     * @ORM\Table(name="fos_user")
     */
    class User extends BaseUser
    {
        ...

        protected $card;

        /**
         * Set card
         *
         * @param \Acme\BoardBundle\Entity\Card $card
         * @return Card
         */
        public function setCard(\Acme\BoardBundle\Entity\Card $card)
        {
            $this->card = $card;

            return $this;
        }

        /**
         * Get card
         *
         * @return \Acme\BoardBundle\Entity\Card
         */
        public function getCard()
        {
            return $this->card;
        }
    }

User.orm.xml:

<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                  http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

    <entity name="Acme\UserBundle\Entity\User" table="fos_user">

        ...

        <many-to-one field="card" target-entity="Acme\BoardBundle\Entity\Card" inversed-by="users">
            <join-column name="card" referenced-column-name="id" />
        </many-to-one>
    </entity>
</doctrine-mapping>

The User entity has a relation with Card entity which has two properties: card number and PIN code. And the properties I actually need to check after login. My login form has not only username and password fields but also the card number and PIN fields.

security.yml (where I feel I have some errors in firewall configuration but I can't understand what's wrong):

providers:
    fos_userbundle:
        id: fos_user.user_manager
firewalls:
    dev:
        pattern:  ^/(_(profiler|wdt)|css|images|js)/
        security: false
    admin:
        pattern:            /admin(.*)
        context:            user
        form_login:
            provider:       fos_userbundle
            login_path:     /admin/login
            use_forward:    false
            check_path:     /admin/login_check
            failure_path:   null
        logout:
            path:           /admin/logout
        anonymous:          true
    main:
        pattern:             .*
        context:             user
        acme: true

        form_login:
            provider:       fos_userbundle
            login_path:     /user/login
            use_forward:    false
            check_path:     /user/login_check
            failure_path:   null
            always_use_default_target_path: true
            default_target_path:            ad_category
        logout:
            path:           /user/logout
        anonymous:          true

User Token:

<?php

namespace Acme\UserBundle\Security\Authentication\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

class AcmeUserToken extends AbstractToken
{
    public $userFIO;
    public $cardNumber;
    public $cardPIN;

    public function __construct(array $roles = array())
    {
        parent::__construct($roles);

        // If the user has roles, consider it authenticated
        $this->setAuthenticated(count($roles) > 0);
    }

    public function getCredentials()
    {
        return '';
    }

    // поскольку токены проверяются при обработке каждом новом запросе клиента, 
    // нам необходимо сохранять нужные нам данные. В связи с этим “обертываем”  
    // унаследованные методы сериализации и десериализации.
    public function serialize() {        
        $pser = parent::serialize();        
        //return serialize(array($this->social, $this->hash, $this->add, $pser));
        return serialize(array($pser));
    }

    public function unserialize($serialized) {
        //list($this->social, $this->hash, $this->add, $pser) = unserialize($serialized);        
        list($pser) = unserialize($serialized);
        parent::unserialize($pser);        
    }
}

AcmeProvider.php (my custom authentication provider):

<?php

namespace Acme\UserBundle\Security\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Acme\UserBundle\Security\Authentication\Token\AcmeUserToken;

class AcmeProvider implements AuthenticationProviderInterface
{
    private $userProvider;

    public function __construct(UserProviderInterface $userProvider)
    {
        $this->userProvider = $userProvider;
    }

    public function authenticate(TokenInterface $token)
    {
        $user = $this->userProvider->loadUserByUsername($token->getUsername());

        if ($user) {
            $authenticatedToken = new AcmeUserToken($user->getRoles());
            $authenticatedToken->setUser($user);

            return $authenticatedToken;
        }

        throw new AuthenticationException('The Acme authentication failed.');
    }

    public function supports(TokenInterface $token)
    {
        return $token instanceof AcmeUserToken;
    }
}

Factory class AcmeFactory.php:

<?php
namespace Acme\UserBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;

class AcmeFactory implements SecurityFactoryInterface
{
    public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
    {
        $providerId = 'security.authentication.provider.acme.'.$id;
        $container
        ->setDefinition($providerId, new DefinitionDecorator('acme.security.authentication.provider'))
        ->replaceArgument(0, new Reference($userProvider))
        ;

        $listenerId = 'security.authentication.listener.acme.'.$id;
        $listener = $container->setDefinition($listenerId, new DefinitionDecorator('acme.security.authentication.listener'));

        return array($providerId, $listenerId, $defaultEntryPoint);
    }

    public function getPosition()
    {
        //return 'pre_auth';
        return 'form';
    }

    public function getKey()
    {
        return 'acme';
    }

    public function addConfiguration(NodeDefinition $node)
    {
    }
}

Configuration of the user provider and listner in config.yml:

services:
    acme.security.authentication.provider:
        class: Acme\UserBundle\Security\Authentication\Provider\AcmeProvider
        abstract:  true
        arguments: ['']
        public: false

    security.authentication.listener.abstract:
        tags:
            - { name: 'monolog.logger', channel: 'security' }
        arguments: [@security.context, @security.authentication.manager, @security.authentication.session_strategy, @security.http_utils, "knetik",@security.authentication.success_handler, @security.authentication.failure_handler, {}, @logger, @event_dispatcher]
        class: Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
     # override application level success handler and re-route back
    security.authentication.success_handler:
        class:  Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler
        arguments:  ["@security.http_utils", {}]
        tags:
            - { name: 'monolog.logger', channel: 'security' }
    # override application level failure handler and re-route back
    security.authentication.failure_handler:
        class:  Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler
        arguments:  ["@http_kernel", "@security.http_utils", {}, "@logger"]
        tags:
            - { name: 'monolog.logger', channel: 'security' }
    yamogu.security.authentication.listener:
        class: Acme\UserBundle\Security\Authentication\Firewall\AcmeListener
        parent: security.authentication.listener.abstract
        abstract:  true
        arguments: ["@security.context", "@security.authentication.manager"]
        public: false

If you need an additinal code I'll add it to the question. Any help will be appreciated!

Link on the dev.log after authorization: https://www.dropbox.com/s/5uot2qofmqjwvmk/dev.log?dl=0

  • 写回答

2条回答 默认 最新

  • dounan4479 2015-08-13 12:25
    关注

    You have to let your security context know about your factory in your bundle class. In your bundle class do this:

    class UserBundle extends Bundle
    {
        public function build(ContainerBuilder $container)
        {
            parent::build($container);
            $extension = $container->getExtension('security');
            $extension->addSecurityListenerFactory(new AcmeFactory());
        }
        public function getParent()
        {
            return 'FOSUserBundle';
        }
    }
    

    [Edit]
    Security layer in Symfony is very difficult to understand!. I suggest you to follow this blog post to gain an understanding of Symfony security.

    评论

报告相同问题?

悬赏问题

  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥15 c++头文件不能识别CDialog