Frage

I'm having trouble (again) with the security component of Symfony2.

First thing first, there are severals objects involved in the process of authentication :

  1. SecurityFactoryInterface
  2. AbstractAuthenticationListener
  3. AuthenticationProviderInterface
  4. Custom Token (inherits from AbstractToken)
  5. UserProviderInterface

Flow

If I understand well, the aim of the Security factory is to configure the custom authentication.

The Listener is the conductor of the authentication. Throught its attemptAuthentication method it captures the form submission (containing user's credentials) and tries to authenticate the user. Inside this method, the Listener will create an AbstractToken and then pass the token to the authenticate method of the AuthenticationProvider.

After that, the AuthenticationProvider calls the UserProvider to retrieve the users data from a webservice or database etc...

Once the UserProvider did its magic, it returns an User object to the AuthenticationProvider.

The AuthenticationProvider then creates an new Token filled with the user retrieved by the UserProvider and then retunrs it to the Listener.

After getting the new Token, the Listener does some unknown magic (I think it sets the token into the security context but I'm not sure).

Problem

At each steps I did a var_dump of my User object. The roles were set in every steps except in the "final steps": in the Listener.

When the Listener retrieve the authenticated token from the UserProvider, user's roles are empty. I can't figure out why...

SecurityFactory

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

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

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

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

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

    public function addConfiguration(NodeDefinition $node)
    {
    }
}

Listener

class CompanyListener extends AbstractAuthenticationListener
{
    // Custructor stuff removed

    protected function requiresAuthentication(Request $request)
    {
        if ($this->options['post_only'] && !$request->isMethod('post'))
        {
            return false;
        }

        return parent::requiresAuthentication($request);
    }

    protected function attemptAuthentication(Request $request)
    {
        $username = trim($request->get($this->options['username_parameter'], null, true));
        $password = $request->get($this->options['password_parameter'], null, true);
        $ip       = $request->getClientIp();

        $request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);

        $authToken = $this->authenticationManager->authenticate(new CompanyUserToken($username, $password, $ip));

        return $authToken;
    }
}

AuthenticationProvider

class CompanyProvider implements AuthenticationProviderInterface
{
    private $userProvider;

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

    public function authenticate(TokenInterface $token)
    {
        $user = $this->userProvider->loadUserByUsernamePassword($token->user, $token->getPassword(), $token->getIp());

        $authenticatedToken = new CompanyUserToken($user->getUsername(), $user->getPassword(), $user->getIp(), $user->getRoles());
        $authenticatedToken->setUser($user);

        return $authenticatedToken;
    }

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

Custom Token

class CompanyUserToken extends AbstractToken
{
    private $password;
    private $ip;

    public function __construct($username, $password, $ip, array $roles = array())
    {
        parent::__construct($roles);

        $this->password = $password;
        $this->user = $username;
        $this->ip = $ip;

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

    public function getCredentials()
    {
        return '';
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getIp()
    {
        return $this->ip;
    }
}

User Provider

class CompanyUserProvider implements UserProviderInterface
{
    private $documentManager;

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

    public function loadUserByUsername($username)
    {
        // Not used but needed by the interface
    }

    public function loadUserByUsernamePassword($username, $password, $ip)
    {
        // Does the magic, retrieve user datas from DB.

        return $user;
    }

    public function refreshUser(UserInterface $user)
    {
        // Does nearly the same thing that the above method
        return $refreshedUser;
    }

    public function supportsClass($class)
    {
        return $class === 'Company\UserBundle\Document\User';
    }
}

service.yml

parameters: 
    security.authentication.handler.class: Company\UserBundle\Security\Authentication\Handler\CompnayAuthenticationHandler
    company_user_provider.class: Company\UserBundle\Security\User\CompanyUserProvider

services:
    security.authentication.handler:
        class: %security.authentication.handler.class%
        public: false
        arguments: [@router, @security.http_utils]

    company.security.authentication.provider:
        class: Company\UserBundle\Security\Authentication\Provider\CompanyProvider
        arguments: [@company_user_provider]

    company.security.authentication.listener:
        class: Company\UserBundle\Security\Firewall\CompanyListener
        arguments: [@security.context, @security.authentication.manager, @security.authentication.session_strategy, @security.http_utils, "company", @security.authentication.handler, @security.authentication.handler, {}, @logger, @event_dispatcher, @user.service.captcha]

    company_user_provider:
        class: %company_user_provider.class%
        arguments: [@doctrine_mongodb]

    user.service.captcha:
        class: Company\UserBundle\Services\CaptchaService
        arguments: [@form.factory]

security.yml

jms_security_extra:
    secure_all_services: false
    expressions: true

security:
    encoders:
        Company\UserBundle\Document\User: plaintext

    role_hierarchy:
        ROLE_VIP_USER:    ROLE_USER
        ROLE_ADMIN:       [ROLE_USER, ROLE_VIP_USER]

    providers:
        webservice:
            id: company_user_provider

    firewalls:  
        company_secured:
            pattern: ^/
            company: true
            anonymous: true
            form_login:
                login_path: login
                check_path: login_check
                post_only: true
                use_referer: false
                success_handler: security.authentication.handler
                failure_handler: security.authentication.handler
            logout: 
                path: /logout
                target: login

    access_control:
        - { path: ^/admin, role: ROLE_ADMIN }

Update

Here are some var_dumps to explain my problem :

UserProvider

var_dump($user);

object(Company\UserBundle\Document\User)[142]
  protected 'username' => string 'Supacoco' (length=13)
  protected 'roles' => 
    array (size=1)
      0 => string 'ROLE_ADMIN' (length=10)

AuthenticationProvider

var_dump($authenticatedToken->getUser());

object(Company\UserBundle\Document\User)[142]
  protected 'username' => string 'Supacoco' (length=13)
  protected 'roles' => 
    array (size=1)
      0 => string 'ROLE_ADMIN' (length=10)

Listener

var_dump($authToken->getUser());

object(Company\UserBundle\Document\User)[142]
  protected 'username' => string 'Supacoco' (length=13)
  protected 'roles' => 
    array (size=0)
      empty
War es hilfreich?

Lösung

After digging a lot, I discovered that the "AuthenticationProviderManager", after authenticating the token, calls by default the user's method "EraseCredentials".

The code below explains why my user loses his/her roles after logging in.

public function eraseCredentials()
{
    $this->roles = array();
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top