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:
- FOS User Bundle successfully installed and configured following by the official documentation.
- Sonata User Bundle installed.
- 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