-
Notifications
You must be signed in to change notification settings - Fork 3
Gérer la connexion (Auth process)
Le composant Sécurité de Symfony permet de définir des "firewalls", protégant des routes communes.
La gestion des firewalls est contenue dans le fichier app/config/security.yml :
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext # pour les utilisateurs statiques
Test\Bundle\Entity\TestEntity: # Pour une entité précise
algorithm: md5
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_TEST: ROLE_USER
ROLE_ADMIN: ROLE_TEST
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
test_users: # utilisateurs statiques définis en dur -> Vont utiliser l'encodeur de Symfony\Component\Security\Core\User\User (voir ci-dessus)
memory:
users:
test_login:
password: test_password
roles: [ 'ROLE_USER' ]
test_login_2:
password: test_password_2
roles: [ 'ROLE_USER' ]
database: # utilisateurs venant d'une table en base de donnée
entity:
class: Test\Bundle\Entity\TestEntity
property: name
cas: # utilisateurs venant d'un service de type AuthenticationProvider personnalisé, faisant appel à une API externe par exemple, le CAS
id: cas_user_provider
multi_provider: # Cela permet de combiner plusieurs providers, donc accepter plusieurs sources d'utilisateurs
chain:
providers: [static, local, cas]
firewalls:
secured_area_login:
pattern: ^/secure/login$
anonymous: ~ # Obligatoire, la route de login doit être accessible à tous...
secured_area:
pattern: ^/secure
provider: multi_provider # la clé du provider précédent
anonymous: false # Autoriser l'anonymat ou non
form_login:
login_path: test_bundle_login_page # la route de login, ou bien l'URL direct : /secure/login
check_path: test_bundle_login_check_page # la route de vérification du login ou bien l'URL direct : /secure/login_check par exemple
post_only: true # Autoriser la connexion en GET ou non
remember_me: false # Utilisation de cookies
logout:
path: test_bundle_logout_page # la route de déconnexion ou bien l'URL direct : /secure/logout par exemple
target: / # L'URL de redirection après déconnexion
invalidate_session: false # Attention, true provoque une erreur 500 PHP (bug d'invalidation des sessions) jusqu'à une certaine version... Et n'est pas requis...
access_control:
- { path: ^/secure, roles: ROLE_USER } # Demande que le rôle minimal de l'utilisateur connecté pour les routes commençant par "/secure" soit ROLE_USER
Cette configuration est assez spéciale mais la documentation en ligne de symfony2 est précise : Sécurité Symfony
Pour utiliser des utilisateurs locaux, il faut renseigner l'entité comme provider dans la configuration, et l'entité définie doit implémenter l'interface "UserInterface" de Symfony, avec notamment 3 méthodes obligatoires : getUsername() qui renvoie vers la propriété servant de login (pouvant être un login, un email...), eraseCredentials(), servant à je ne sais pas trop quoi et getRoles() définissant les rôles de l'utilisateur (ces rôles peuvent être statiques ou en base de données également !)
Il est également conseillé d'implémenter l'interface native Serializable de PHP avec les 2 méthodes jointes ci-dessous.
<?php
namespace Test\Bundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* TestEntity
*
* @ORM\Table(name="test_entity")
* @ORM\Entity(repositoryClass="Test\Bundle\Entity\TestEntityRepository")
*/
class TestEntity implements UserInterface, \Serializable
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @inheritDoc
*/
public function getUsername()
{
return $this->name;
}
/**
* @inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* @inheritDoc
*/
public function eraseCredentials()
{
}
/**
* @see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
));
}
/**
* @see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
) = unserialize($serialized);
}
}
Enfin, il faut définir un UserProvider, par exemple le Repository de l'entité, implémentant l'interface UserProviderInterface, contenant 2 méthodes obligatoires loadUserByUsername($username) qui cherche l'utilisateur par son login, et refreshUser($user) qui définie les classes d'utilisateur autorisées (pratique en cas de hierarchie) :
<?php
namespace Test\Bundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
/**
* TestEntityRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class TestEntityRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$testEntity = null;
$qb = $this->_em->createQueryBuilder();
// Requête SQL dans la table "test_entity"
$qb->select('te')
->from('TestBundle:TestEntity', 'te')
->where('te.name = :name')
->setParameter('name', $username)
;
try {
$testEntity = $qb->getQuery()->getSingleResult();
}
catch (NoResultException $e) {
throw new UsernameNotFoundException(
sprintf(
'Ce nom d\'utilisateur est inconnu : "%s".',
$username
), 0, $e
);
}
return $testEntity;
}
public function refreshUser(UserInterface $testEntity)
{
if (!$this->supportsClass(get_class($testEntity))) {
throw new UnsupportedUserException(
sprintf(
'Instances of "%s" are not supported.',
$testEntity
)
);
}
return $this->find($testEntity->getId());
}
public function supportsClass($class)
{
return ($class === $this->getEntityName() || is_subclass_of($class, $this->getEntityName()));
}
}
Pour utiliser des utilisateurs externes, il suffit de créer une classe Modèle (qui ne sera cependant pas une entité, il est donc préférable de ne pas l'inclure dans le sous-dossier Entity du bundle !!) implémentant UserInterface, comme dans le cas d'une entité, et un UserProvider implémentant UserProviderInterface...
C'est exactement une entité, mais sans les annotations Doctrine, au final.
Enfin, il faut ajouter ce provider comme un service, car il n'a pas d'encodeur ni de gestion locale :
# Fichier src/Test/Bundle/Resources/config/services.yml
parameters:
cas_user_provider.class: Test\Bundle\CAS\CASUserProvider # Class du provider
services:
cas_user_provider:
class: "%cas_user_provider.class%"
Et PAF ! Ca fait des chocapics... Mais uniquement grâce au génial DependencyInjector de Symfony qui vous permet de définir tous les éléments de ce fichier en tant que services, accessibles de n'importe où... Y compris dans le fichier app/config/security.yml !