【问题标题】:Security: password check removal and custom User Provider安全性:密码检查删除和自定义用户提供程序
【发布时间】:2013-01-22 11:33:16
【问题描述】:

我正在创建 Symfony2(版本 2.0.16)custom User Provider 以与我们的 LDAP 服务器一起工作,但根据How to create a custom User Provider 文档,密码检查是在 Symfony2 端完成的:

当用户提交用户名和密码时,身份验证层 要求配置的用户提供者返回给定的用户对象 用户名。 Symfony 然后检查这个用户的密码是否是 正确并生成安全令牌,以便用户保持身份验证 在当前会话期间。

首先,我不喜欢将用户密码传回 Symfony 的想法。其次,我们已经有 LDAP Web 服务,它会检查密码是否匹配,如果更改密码会出现问题。

问题: 如何从 Symfony 中移除密码检查并让它依赖 LDAP Web 返回布尔值IsAuth 标志的服务?

这就是我现在查询 LDAP Web 服务的方式:

// The output has IsAuth flag
$this->get('LDAP_user_provider')
  ->searchMember($request->get('username'), $request->get('password'));

【问题讨论】:

    标签: symfony


    【解决方案1】:

    好的,这不是很简单,但我会尽力为您提供尽可能多的信息。 Symfony 2.0 需要做一些小改动,我的解决方案是 2.1。我希望没有复制/粘贴问题,也没有拼写错误或缺少配置。首先,您需要创建一个AuthenticationProvider,类似于:

    <?php
    
    namespace Acme\DemoBundle\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\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Encoder\EncoderFactory;
    
    use Beryllium\CacheBundle\Cache;
    
    use Acme\DemoBundle\Security\Authentication\Token\RestToken;
    
    /**
     * The Provider is the component of the authentication system that authenticates tokens.
     */
    class LdapProvider implements AuthenticationProviderInterface
    {
        private $userProvider;
        private $encoderFactory;
    
        /**
         * Constructor
         * @param UserProviderInterface $userProvider
         * @param String                $cacheDir
         * @param EncoderFactory        $encoderFactory
         */
        public function __construct(UserProviderInterface $userProvider, EncoderFactory $encoderFactory)
        {
            $this->userProvider   = $userProvider;
            $this->encoderFactory = $encoderFactory; // usually this is responsible for validating passwords
        }
    
        /**
         * This function authenticates a passed in token.
         * @param  TokenInterface          $token
         * @return TokenInterface
         * @throws AuthenticationException if wrong password or no username
         */
        public function authenticate(TokenInterface $token)
        {
            if (!empty($token->username)) {
                $user    = $this->userProvider->loadUserByUsername($token->username);
                $encoder = $this->encoderFactory->getEncoder($user);
    
                if ($token->needsAuthentication && !$token->isLdapAuthenticated()) {
                    throw new AuthenticationException('Password wrong');
                }
            } else {
                throw new AuthenticationException('No user');
            }
    
            $token->setUser($user);
            $token->setAuthenticated(true);
    
            return $token;
        }
    
        /**
         * @inheritdoc
         * @param  TokenInterface $token
         * @return Boolean
         */
        public function supports(TokenInterface $token)
        {
            return $token instanceof RestToken;
        }
    }
    

    注册服务(使用 XML):

        <service id="ldap.security.authentication.provider"
          class="Acme\DemoBundle\Security\Authentication\Provider\LdapProvider" public="false">
            <argument /> <!-- User Provider -->
            <argument type="service" id="security.encoder_factory"/>
        </service>
    

    或使用 YAML:

       ldap.security.authentication.provider:
           class: Acme\DemoBundle\Security\Authentication\Provider\LdapProvider
           public: false
           arguments:
               - ~
               - "@security.encoder_factory" 
    

    创建安全工厂:

    <?php
    
    namespace Acme\DemoBundle\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 LdapFactory implements SecurityFactoryInterface
    {
        public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
        {
            $providerId = 'security.authentication.provider.ldap.'.$id;
            $container
                ->setDefinition($providerId, new DefinitionDecorator('ldap.security.authentication.provider'))
                ->replaceArgument(0, new Reference($userProvider))
            ;
    
            $listenerId = 'security.authentication.listener.ldap.'.$id;
            $listener   = $container->setDefinition($listenerId, new DefinitionDecorator('ldap.security.authentication.listener'));
    
            return array($providerId, $listenerId, $defaultEntryPoint);
        }
    
        public function getPosition()
        {
            return 'pre_auth';
        }
    
        public function getKey()
        {
            return 'ldap';
        }
    
        public function addConfiguration(NodeDefinition $node)
        {}
    }
    

    并在你的Bundle中注册它:

    <?php
    
    namespace Acme\DemoBundle;
    
    use Symfony\Component\HttpKernel\Bundle\Bundle;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    
    use Acme\DemoBundle\Security\Factory\LdapFactory;
    
    class AcmeDemoBundle extends Bundle
    {
        public function build(ContainerBuilder $container)
        {
            parent::build($container);
    
            $extension = $container->getExtension('security');
            $extension->addSecurityListenerFactory(new LdapFactory());
        }
    }
    

    并创建您自己的令牌:

    namespace Acme\DemoBundle\Security\Authentication\Token;
    
    use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
    
    /**
     * This is a class that represents a security token that is used for logged in users.
     */
    class LdapToken extends AbstractToken
    {
        public $sessionId;
        public $username;
        public $password;
        public $member;
        public $needsAuthentication = true;
    
        public function __construct(array $roles = array())
        {
            parent::__construct($roles);
        }
    
        public function getCredentials()
        {
            return '';
        }
    
        public function getRoles()
        {
            if ($this->getUser()) {
                return $this->getUser()->getRoles();
            } else {
                return array();
            }
        }
    
        public function isLdapAuthenticated()
        {
             return true; // Left as an exercise
        }
    }
    

    然后您需要在侦听器中创建该令牌,例如:

    <?php
    
    namespace Acme\ApiBundle\Security\Firewall;
    
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpFoundation\Cookie;
    
    use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    use Symfony\Component\HttpKernel\KernelEvents;
    
    use Symfony\Component\Security\Http\Firewall\ListenerInterface;
    use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
    
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use Symfony\Component\Security\Core\SecurityContextInterface;
    use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    
    use Symfony\Component\EventDispatcher\EventDispatcher;
    
    use Acme\DemoBundle\Security\Authentication\Token\LdapToken;
    
    use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
    
    /**
     * Class that will listen for log ins and then authorize the user.
     */
    class LdapListener implements ListenerInterface
    {
        /**
         * A security context
         * @var SecurityContextInterface
         */
        protected $securityContext;
    
        /**
         * A authentication manager that we will be able to authenticate against
         * @var AuthenticationManagerInterface
         */
        protected $authenticationManager;
    
    
        /**
         * Constructor
         *
         * @param SecurityContextInterface              $securityContext
         * @param AuthenticationManagerInterface        $authenticationManager
         */
        public function __construct(SecurityContextInterface $securityContext,
            AuthenticationManagerInterface $authenticationManager
        ) {
            $this->securityContext              = $securityContext;
            $this->authenticationManager        = $authenticationManager;
        }
    
        /**
         * This function is handling the authentication part.
         *
         * @param GetResponseEvent $event
         * @return
         */
        public function handle(GetResponseEvent $event)
        {
            $request = $event->getRequest();
    
            $token = new LdapToken();
    
            // now populate it with whatever information you need, username, password...
    
            try {
                $returnValue = $this->authenticationManager->authenticate($token);
    
                if ($returnValue instanceof TokenInterface) {
                    if ($token->needsAuthentication) {
                        if ($event->hasResponse()) {
                            $response = $event->getResponse();
                        } else {
                            $response = new Response();
                            $event->setResponse($response);
                        }
                    }
    
                    return $this->securityContext->setToken($returnValue);
                } elseif ($returnValue instanceof Response) {
                    return $event->setResponse($response);
                }
            } catch (AuthenticationException $e) {
                // Do nothing in this case. We are returning a 401 below
            }
    
            $response = new Response('UNAUTHORIZED');
            $response->setStatusCode(HTTPCodes::HTTP_UNAUTHORIZED);
            $event->setResponse($response);
        }
    }
    

    并将其也注册为服务(使用 XML):

        <service id="ldap.security.authentication.listener"
          class="Acme\DemoBundle\Security\Firewall\RestListener" public="false">
            <argument type="service" id="security.context"/>
            <argument type="service" id="security.authentication.manager" />
        </service>
    

    或 YAML:

        ldap.security.authentication.listener:
            class: Acme\DemoBundle\Security\Firewall\RestListener
            public: false
            arguments: 
                - "@security.context"
                - "@security.authentication.manager"
    

    希望你能开始!

    【讨论】:

    • 哦,这可真费劲!试试看...谢谢!
    • 能否提供 YAML 版本的服务,提前谢谢
    • @nusje2000 我在答案中添加了 YAML 服务配置
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多