Security: Password Removal & Custom Provider

I am building Symfony2 (version 2.0.16) custom User Provider

to work with our LDAP server, but according to How to create a custom provider document, password verification is done on the Symfony2 side:

When a user submits a username and password, the authentication level prompts the configured user provider to return a custom object for the given username. Symfony then checks to see if that user's password is correct and generates a security token so that the user remains authenticated during the current session.

First of all, I don't like the idea of ​​passing user passwords back to Symfony. Second, we already have an LDAP web service that checks if the password on its side matches, and it would be problematic to change it.

Question: How can I remove password checking from Symfony and let it rely on an LDAP Web Service that returns a Boolean flag IsAuth

?

Now I am requesting the LDAP web service:

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

      

+3


source to share


1 answer


Ok, this is not super trivial, but I will try to provide you with as much information as possible. There are a few small changes you need to make for Symfony 2.0, my solution is 2.1. I hope there are no copy and paste problems, no typos or missing configuration. First, you'll want to create AuthenticationProvider

something like:

<?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;
    }
}

      

Register the service (with 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>

      

Or with YAML:

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

      

Create Factory Defenses:

<?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)
    {}
}

      

and register it with you. 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());
    }
}

      



and create your own token:

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
    }
}

      

Then you need to create this token in the listener, for example:

<?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);
    }
}

      

and register this as a service (using 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>

      

or YAML:

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

      

Hope you get started!

+7


source







All Articles