Безопасность: удаление паролей и пользовательский поставщик

Я создаю Symfony2 (Version 2.0.16) пользовательский поставщик для работы с нашим LDAP-сервером, но в соответствии с Как создать пользовательский поставщик , проверка пароля выполняется на стороне Symfony2:

Когда пользователь отправляет имя пользователя и пароль, уровень аутентификации   запрашивает у настроенного провайдера пользователя возврат пользовательского объекта для данного   имя пользователя. Затем Symfony проверяет, является ли пароль этого пользователя   правильно и генерирует токен безопасности, чтобы пользователь оставался аутентифицированным   во время текущей сессии.

Прежде всего мне не нравится идея передать пароли пользователей обратно в Symfony. Во-вторых, у нас уже есть веб-служба LDAP, которая проверяет, соответствует ли пароль на его стороне, и его изменение было бы проблематичным.

Question: How can remove password checking from Symfony and let it rely on LDAP Web Service which returns Boolean IsAuth flag?

Вот как я теперь запрашиваю веб-службу LDAP:

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

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):

    
         <!-- User Provider -->
        
    

Или с 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):

    
        
        
    

или YAML:

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

Надеюсь, что ты начнёшь!

7
добавлено
Можете ли вы предоставить мне версию услуг YAML, заблаговременно
добавлено автор nusje2000, источник
О, это много! Попытка ... Спасибо!
добавлено автор Roman Newaza, источник
@ nusje2000 Я добавил конфигурацию службы YAML в свой ответ
добавлено автор brensch, источник
symfony
symfony
1 045 участник(ов)

Сообщество Symfony, Symfony Components, Symfony Framework. Вакансии: https://t.me/symfony_careers Официальный slack: https://symfony.com/slack-invite

symfony
symfony
354 участник(ов)