I'm trying to create a custom connection where I should use web service. So I read the tutorial on security and this one on custom provider. Now I'm trying to create my own login form with 3 fields : Email, password and number. After validation I understood that my /login_check
pass in the function loadUserByUsername($username)
, but this function took in argument just $username
and doesn't take my fields email and number. To execute my web service I need to get my 3 args. How can I customize my login form?
The goal is: When users submit the login form I want to send a web service with login form args. If I get my response without error I want to connect my user loaded by web service to symfony2 toolbar else I want to display an error message.
You can see my code here :
Security.yml :
security:
encoders:
MonApp\MonBundle\Security\User\WebserviceUser: sha512
#Symfony\Component\Security\Core\User\User: plaintext
# http://symfony.com/doc/current/book/security.html#hierarchical-roles
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# http://symfony.com/doc/current/book/security.html#where-do-users-come- from-user-providers
providers:
#in_memory:
#memory:
#users:
#ryan: { password: ryanpass, roles: 'ROLE_USER' }
#admin: { password: kitten, roles: 'ROLE_ADMIN' }
webservice:
id: webservice_user_provider
# the main part of the security, where you can set up firewalls
# for specific sections of your app
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
area_secured:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
default_target_path: /test
logout:
path: /logout
target: /
# with these settings you can restrict or allow access for different parts
# of your application based on roles, ip, host or methods
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_AUTHENTICATED }
WebserviceUser.php :
<?php
namespace MonApp\MonBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class WebserviceUser implements UserInterface
{
private $email;
private $password;
private $num;
private $salt;
private $roles;
public function __construct($email, $password, $num, $salt, array $roles)
{
$this->email = $email;
$this->password = $password;
$this->num = $num;
$this->salt = $salt;
$this->roles = $roles;
}
public function getUsername()
{
return '';
}
public function getEmail()
{
return $this->email;
}
public function getPassword()
{
return $this->password;
}
public function getNum()
{
return $this->num;
}
public function getSalt()
{
return $this->salt;
}
public function getRoles()
{
return $this->roles;
}
public function eraseCredentials()
{}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
return false;
}
if ($this->email !== $user->getEmail()) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->num !== $user->getNum()) {
return false;
}
if ($this->getSalt() !== $user->getSalt()) {
return false;
}
return true;
}
}
WebserviceUserProvider.php
<?php
namespace MonApp\MonBundle\Security\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use MonApp\MonBundle\Security\User\WebserviceUser;
class WebserviceUserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
//print_r($username);
//die();
// effectuez un appel à votre service web ici
return new WebserviceUser('email', 'password', '45555', 'salt', array('ROLE_USER'));
//throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
print_r($user);
die();
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'MonApp\MonBundle\Security\User\WebserviceUser';
}
}
service.yml
parameters:
webservice_user_provider.class: MonApp\MonBundle\Security\User\WebserviceUserProvider
services:
webservice_user_provider:
class: "%webservice_user_provider.class%"
I won't put all the code, but my login action, template and routing are exactly the same than security link. But my user new WebserviceUser('email', 'password', '45555', 'salt', array('ROLE_USER'))
isn't connected to the toolbar. So I think I forgot something...
Do I need to use a Listener, UserToken and Factory to do that ?
Ok boy, prepare for a long answer.
I assume that you have a folder named Security
placed in /MonApp/MonBundle
First you need a custom Token placed in Security/Token/WebServiceToken
<?php
namespace MonApp\MonBundle\Security\Token;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class WebServiceToken implements TokenInterface
{
protected $attributes;
protected $authenticated;
protected $user;
public function __construct($attributes)
{
$this->setAttributes($attributes);
$this->authenticated = false;
$this->user = null;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(
array(
is_object($this->user) ? clone $this->user : $this->user,
$this->authenticated,
$this->attributes
)
);
}
/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
list($this->user, $this->authenticated, $this->attributes) = unserialize($serialized);
}
public function __toString()
{
$result = '';
foreach($this->attributes as $name => $value)
{
$result .= "$name: $value ";
}
return "Token($result)";
}
/**
* Returns the user roles.
*
* @return RoleInterface[] An array of RoleInterface instances.
*/
public function getRoles()
{
return $this->user->getRoles();
}
public function getUser()
{
return $this->user;
}
public function setUser($user)
{
$this->user = $user;
}
public function getUsername()
{
return $this->user->getUsername();
}
public function isAuthenticated()
{
return $this->authenticated;
}
public function setAuthenticated($isAuthenticated)
{
$this->authenticated = $isAuthenticated;
}
public function eraseCredentials()
{
;
}
public function getAttributes()
{
return $this->attributes;
}
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
public function hasAttribute($name)
{
return array_key_exists($name, $this->attributes);
}
public function getAttribute($name)
{
if (!array_key_exists($name, $this->attributes)) {
throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name));
}
return $this->attributes[$name];
}
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
public function getCredentials()
{
return null;
}
}
Then you need a Firewall in Security/Authentication/WebServiceAuthenticationListener
<?php
namespace MonApp\MonBundle\Security\Authentication;
use MonApp\MonBundle\Security\Token\WebServiceToken;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
class WebServiceAuthenticationListener implements ListenerInterface
{
protected $securityContext;
protected $authentificationManager;
protected $logger;
/**
* @param SecurityContextInterface $securityContext
* @param AuthenticationManagerInterface $authenticationManager
* @param LoggerInterface $logger
*/
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->logger = $logger;
}
/**
* {@inheritdoc}
* @see \Symfony\Component\Security\Http\Firewall\ListenerInterface::handle()
*/
final public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
/**
* Fill $attributes with the data you want to set in the user
*/
$attributes = array();
$token = new WebServiceToken($attributes);
try {
if (null !== $this->logger ) {
$this->logger->debug(sprintf('Vérification du contexte de sécurité pour le token: %s', $token));
}
$token = $this->authenticationManager->authenticate($token);
if (null !== $this->logger) {
$this->logger->info(sprintf('Authentification réussie: %s', $token));
}
// Token authentifié
$this->securityContext->setToken($token);
}
catch (AuthenticationException $failed) {
throw $failed;
}
}
}
Then you need an Authentication provider in Security/Authentication/WebServiceAuthenticationProvider
<?php
namespace MonApp\MonBundle\Security\Authentication;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use MonApp\MonBundle\Security\User\WebServiceUser;
use MonApp\MonBundle\Security\User\WebServiceUserProvider;
use MonApp\MonBundle\Security\Token\WebServiceToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
class WebServiceAuthenticationProvider implements AuthenticationProviderInterface
{
protected $provider;
public function __construct(WebServiceUserProvider $provider)
{
$this->provider = $provider;
}
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return new AuthenticationException('Token non supporté');
}
$user = $this->provider->createUser($token->getAttributes());
$token->setUser($user);
/**
* CALL TO THE WEB SERVICE HERE
*/
$myCallisASuccess = true;
if($myCallisASuccess) {
$token->setAuthenticated(true);
}
return $token;
}
public function supports(TokenInterface $token)
{
return $token instanceof WebServiceToken;
}
}
Now the factory ... Security/Factory/WebServiceFactory
<?php
namespace MonApp\MonBundle\Security\Factory;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class WebServiceFactory implements SecurityFactoryInterface
{
/**
* {@inheritdoc}
* @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::create()
*/
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.web_service'.$id;
$container->setDefinition($providerId, new DefinitionDecorator('web_service.security.authentication.provider'));
$listenerId = 'security.authentication.listener.web_service.'.$id;
$container->setDefinition($listenerId, new DefinitionDecorator('web_service.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
/**
* {@inheritdoc}
* @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::getPosition()
*/
public function getPosition()
{
return 'pre_auth';
}
/**
* {@inheritdoc}
* @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::getKey()
*/
public function getKey()
{
return 'web_service';
}
}
You have to edit WebServiceUserProvider by adding this function
public function createUser(array $attributes)
{
$email = $attributes['email'];
$password = $attributes['password'];
$num = $attributes['num'];
$salt = $attributes['salt'];
$user = new WebServiceUser($email, $password, $num, $salt);
return $user;
}
And remove $roles from you WebServiceUSer class:
public function __construct($email, $password, $num, $salt)
{
$this->email = $email;
$this->password = $password;
$this->num = $num;
$this->salt = $salt;
$this->roles = array();
}
Ok, now you have all you security classes done. Let's configure this....
In the class MonBundle
<?php
namespace MonApp\Bundle\MonBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use MonApp\Bundle\MonBundle\Security\Factory\WebServiceFactory;
class MonBundle extends Bundle
{
/**
* {@inheritdoc}
* @see \Symfony\Component\HttpKernel\Bundle\Bundle::build()
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
// Ajout de la clef 'web_service' à l'extension security
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new WebServiceFactory());
}
}
In the config of MonBundle
services:
web_service.security.user.provider:
class: MonApp\Bundle\MonBundle\Security\User\WebServiceUserProvider
web_service.security.authentication.listener:
class: MonApp\Bundle\MonBundle\Security\Authentication\WebServiceAuthenticationListener
arguments: ['@security.context', '@web_service.security.authentication.provider','@?logger']
web_service.security.authentication.provider:
class: MonApp\Bundle\MonBundle\Security\Authentication\WebServiceAuthenticationProvider
arguments: ['@web_service.security.user.provider']
And last, in your app config:
security:
area_secured:
pattern: ^/
web_service: ~
form_login:
login_path: /login
check_path: /login_check
default_target_path: /test
logout:
path: /logout
target: /