0% ont trouvé ce document utile (0 vote)
21 vues47 pages

Admin

Ce document décrit les concepts clés de l'authentification et de l'autorisation dans Symfony2. Il explique la différence entre ces deux notions et présente des exemples pour illustrer le processus de sécurité. Le document fournit également un aperçu de la configuration de la sécurité dans Symfony2.
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
21 vues47 pages

Admin

Ce document décrit les concepts clés de l'authentification et de l'autorisation dans Symfony2. Il explique la différence entre ces deux notions et présente des exemples pour illustrer le processus de sécurité. Le document fournit également un aperçu de la configuration de la sécurité dans Symfony2.
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

[Link]

com/fr/courses/2078536-developpez-votre-site-web-avec-le-framework-
symfony2-ancienne-version/2082939-securite-et-gestion-des-utilisateurs

Authentification et autorisation
La sécurité sous Symfony2 est très poussée, vous pouvez la contrôler très finement, mais
surtout très facilement. Pour atteindre ce but, Symfony2 a bien séparé deux mécanismes
différents : l'authentification et l'autorisation. Prenez le temps de bien comprendre ces
deux notions pour bien attaquer la suite du cours.

Les notions d'authentification et d'autorisation


L'authentification

L'authentification est le processus qui va définir qui vous êtes, en tant que visiteur. L'enjeu
est vraiment très simple : soit vous ne vous êtes pas identifié sur le site et vous êtes un
anonyme, soit vous vous êtes identifié (via le formulaire d'identification ou via un cookie
« Se souvenir de moi ») et vous êtes un membre du site. C'est ce que la procédure
d'authentification va déterminer. Ce qui gère l'authentification dans Symfony2 s'appelle
un firewall, ou un pare-feu en français.

Ainsi vous pourrez sécuriser des parties de votre site Internet juste en forçant le visiteur
à être un membre authentifié. Si le visiteur l'est, le firewall va le laisser passer, sinon il le
redirigera sur la page d'identification. Cela se fera donc dans les paramètres du firewall,
nous les verrons plus en détail par la suite.

L'autorisation

L'autorisation est le processus qui va déterminer si vous avez le droit d'accéder à la


ressource (la page) demandée. Il agit donc après le firewall. Ce qui gère l'autorisation dans
Symfony2 s'appelle l'access control.

Par exemple, un membre identifié lambda aura accès à la liste de sujets d'un forum, mais
ne peut pas supprimer de sujet. Seuls les membres disposant des droits d'administrateur
le peuvent, c'est ce que l'access control va vérifier.

Exemples

Pour bien comprendre la différence entre l'authentification et l'autorisation, je reprends


ici les exemples de la documentation officielle, qui sont, je trouve, très intéressants et
illustratifs. Dans ces exemples, vous distinguerez bien les différents acteurs de la sécurité.

Je suis anonyme, et je veux accéder à la page/foo qui ne requiert pas de droits

Dans cet exemple, un visiteur anonyme souhaite accéder à la page/foo. Cette page ne
requiert pas de droits particuliers, donc tous ceux qui ont réussi à passer le firewall
peuvent y avoir accès. La figure suivante montre le processus.
Schéma du processus de sécurité

Sur ce schéma, vous distinguez bien le firewall d'un côté et l'access control (contrôle
d'accès) de l'autre. C'est très clair, mais reprenons-le ensemble pour bien comprendre :

1. Le visiteur n'est pas identifié, il est anonyme, et tente d'accéder à la page/foo.


2. Le firewall est configuré de telle manière qu'il n'est pas nécessaire d'être identifié
pour accéder à la page/foo. Il laisse donc passer notre visiteur anonyme.
3. Le contrôle d'accès regarde si la page/foo requiert des droits d'accès : il n'y en a
pas. Il laisse donc passer notre visiteur, qui n'a aucun droit particulier.
4. Le visiteur a donc accès à la page/foo.
Je suis anonyme, et je veux accéder à la page/admin/foo qui requiert certains droits

Dans cet exemple, c'est le même visiteur anonyme qui veut accéder à la page/admin/foo.
Mais cette fois, la page/admin/foo requiert le rôle ROLE_ADMIN ; c'est un droit particulier,
nous le verrons plus loin. Notre visiteur va se faire refuser l'accès à la page, la figure
suivante montre comment.

Schéma du
processus de sécurité

Voici le processus pas à pas :

1. Le visiteur n'est pas identifié, il est toujours anonyme, et tente d'accéder à la


page/admin/foo.
2. Le firewall est configuré de manière qu'il ne soit pas nécessaire d'être identifié
pour accéder à la page/admin/foo. Il laisse donc passer notre visiteur.
3. Le contrôle d'accès regarde si la page/admin/foo requiert des droits d'accès : oui,
il faut le rôle ROLE_ADMIN. Le visiteur n'a pas ce rôle, donc le contrôle d'accès lui
interdit l'accès à la page/admin/foo.
4. Le visiteur n'a donc pas accès à la page/admin/foo, et se fait rediriger sur la page
d'identification.
Je suis identifié, et je veux accéder à la page/admin/foo qui requiert certains droits

Cet exemple est le même que précédemment, sauf que cette fois notre visiteur est
identifié, il s'appelle Ryan. Il n'est donc plus anonyme.

Schéma
du processus de sécurité

1. Ryan s'identifie et il tente d'accéder à la page/admin/foo. D'abord, le firewall


confirme l'authentification de Ryan (c'est son rôle !). Visiblement c'est bon, il laisse
donc passer Ryan.
2. Le contrôle d'accès regarde si la page/admin/foo requiert des droits d'accès : oui,
il faut le rôle ROLE_ADMIN, que Ryan n'a pas. Il interdit donc l'accès à la
page/admin/foo à Ryan.
3. Ryan n'a pas accès à la page/admin/foo non pas parce qu'il ne s'est pas identifié,
mais parce que son compte utilisateur n'a pas les droits suffisants. Le contrôle
d'accès lui affiche donc une page d'erreur lui disant qu'il n'a pas les droits
suffisants.
Je suis identifié, et je veux accéder à la page/admin/foo qui requiert des droits que j'ai.

Ici, nous sommes maintenant identifiés en tant qu'administrateur, on a donc le rôle


ROLE_ADMIN! Du coup, nous pouvons accéder à la page/admin/foo, comme le montre la
figure suivante.

Schéma du processus de sécurité

1. L'utilisateur admin s'identifie, et il tente d'accéder à la page/admin/foo. D'abord,


le firewall confirme l'authentification d'admin. Ici aussi, c'est bon, il laisse donc
passer admin.
2. Le contrôle d'accès regarde si la page/admin/foo requiert des droits d'accès : oui,
il faut le rôle ROLE_ADMIN, qu'admin à bien. Il laisse donc passer l'utilisateur.
3. L'utilisateur admin a alors accès à la page/admin/foo, car il est identifié et il
dispose des droits nécessaires.
Processus général

Lorsqu'un utilisateur tente d'accéder à une ressource protégée, le processus est


finalement toujours le même, le voici :

1. Un utilisateur veut accéder à une ressource protégée ;


2. Le firewall redirige l'utilisateur au formulaire de connexion ;
3. L'utilisateur soumet ses informations d'identification (par exemple login et mot de
passe) ;
4. Le firewall authentifie l'utilisateur ;
5. L'utilisateur authentifié renvoie la requête initiale ;
6. Le contrôle d'accès vérifie les droits de l'utilisateur, et autorise ou non l'accès à la
ressource protégée.
Ces étapes sont simples, mais très flexibles. En effet, derrière le mot « authentification »
se cache en pratique bien des méthodes : un formulaire de connexion classique, mais
également l'authentification via Facebook, Google, etc., ou via les certificats X.509, etc.
Bref, le processus reste toujours le même, mais les méthodes pour authentifier vos
internautes sont nombreuses, et répondent à tous vos besoins. Et, surtout, elles n'ont pas
d'impact sur le reste de votre code : qu'un utilisateur soit authentifié via Facebook ou un
formulaire classique ne change rien à vos contrôleurs !

Première approche de la sécurité


Si les processus que nous venons de voir sont relativement simples, leur mise en place
et leur configuration nécessitent un peu de travail.

Nous allons construire pas à pas la sécurité de notre application. Cette section
commence donc par une approche théorique de la configuration de la sécurité avec
Symfony2 (notamment l'authentification), puis on mettra en place un formulaire de
connexion simple. On pourra ainsi s'identifier sur notre propre site, ce qui est plutôt
intéressant ! Par contre, les utilisateurs ne seront pas encore liés à la base de données,
on le verra un peu plus loin, avançons doucement.

Le fichier de configuration de la sécurité


La sécurité étant un point important, elle a l'honneur d'avoir son propre fichier de
configuration. Il s'agit du fichier [Link], situé dans le répertoire app/config de
votre application. Je vous propose déjà d'y faire un petit nettoyage : supprimez les
deux sections login et secured_area sous la section firewalls, elles concernent le
bundle de démonstration que nous avons supprimé au début du cours. Votre fichier
doit maintenant ressembler à ceci :

# app/config/[Link]

security:

encoders:

Symfony\Component\Security\Core\User\User: plaintext

role_hierarchy:

ROLE_ADMIN: ROLE_USER

ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

providers:
in_memory:

memory:

users:

user: { password: userpass, roles: [ 'ROLE_USER' ] }

admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }

firewalls:

dev:

pattern: ^/(_(profiler|wdt)|css|images|js)/

security: false

access_control:

#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }


Le nom de la section s'appelle security, il n'est pas sorti d'un chapeau... c'est tout
simplement le nom choisi par le bundle SecurityBundle pour sa configuration. Eh oui,
la sécurité dans Symfony2 vient d'un bundle ! La configuration que nous ferons dans
ce chapitre n'est autre que la configuration de ce bundle.
Bien évidemment, rien de toute cette configuration ne vous parle pour le moment.
Rassurez-vous : à la fin du chapitre ce fichier ne vous fera plus peur. Pour le moment,
décrivons rapidement chaque section de la configuration.

Section encoders

security:

encoders:

Symfony\Component\Security\Core\User\User: plaintext
Un encodeur est un objet qui encode les mots de passe de vos utilisateurs. Cette
section de configuration permet donc de modifier l'encodeur utilisé pour vos
utilisateurs, et donc la façon dont sont encodés les mots de passe dans votre
application.

Vous l'avez deviné, ici l'encodeur utilisé plaintext n'encode en réalité rien du tout. Il
laisse en fait les mots de passe en clair, c'est pourquoi les mots de passe que nous
verrons dans une section juste en dessous sont en clair. Évidemment, nous définirons
par la suite un vrai encodeur, du type sha512, une méthode sûre !
Section role_hierarchy

security:

role_hierarchy:

ROLE_ADMIN: ROLE_USER

ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]


La notion de « rôle » est au centre du processus d'autorisation. On assigne un ou
plusieurs rôles à chaque utilisateur, et pour accéder aux ressources on demande que
l'utilisateur ait un ou plusieurs rôles. Ainsi, lorsqu'un utilisateur tente d'accéder à une
ressource, le contrôleur d'accès vérifie s'il dispose du ou des rôles requis par la
ressource. Si c'est le cas, l'accès est accordé. Sinon, l'accès est refusé.

Cette section de la configuration dresse la hiérarchie des rôles. Ainsi, le rôle ROLE_USER
est compris dans le rôle ROLE_ADMIN. Cela signifie que si votre page requiert le rôle
ROLE_USER, et qu'un utilisateur disposant du rôle ROLE_ADMIN tente d'y accéder, il sera
autorisé, car en disposant du rôle d'administrateur, il dispose également du rôle
ROLE_USER.

Les noms des rôles n'ont pas d'importance, si ce n'est qu'ils doivent commencer par «
ROLE_ ».

Section providers

security:

providers:

in_memory:

memory:

users:

user : { password: userpass, roles: [ 'ROLE_USER' ] }

admin : { password: adminpass, roles: [ 'ROLE_ADMIN' ] }


Un provider est un fournisseur d'utilisateurs. Les firewalls s'adressent aux providers
pour récupérer les utilisateurs et les identifier.

Pour l'instant vous pouvez le voir dans le fichier, un seul fournisseur est défini, nommé
in_memory (encore une fois, le nom est arbitraire). C'est un fournisseur assez
particulier dans le sens où les utilisateurs sont directement listés dans ce fichier de
configuration, il s'agit des utilisateurs « user » et « admin ». Vous l'aurez compris, c'est
un fournisseur pour faire du développement, pour tester la couche sécurité sans avoir
besoin d'une quelconque base de données derrière. Il faudra bien sûr le supprimer
par la suite.
Je vous rassure, il existe d'autres types de fournisseurs que celui-ci. On utilisera
notamment par la suite un fournisseur permettant de récupérer les utilisateurs dans la
base de données, il est déjà bien plus intéressant.

Section firewalls

security:

firewalls:

dev:

pattern: ^/(_(profiler|wdt)|css|images|js)/

security : false
Comme on l'a vu précédemment, un firewall (ou pare-feu) cherche à vérifier que vous
êtes bien celui que vous prétendez être. Ici, seul le pare-feu dev est défini, nous avons
supprimé les autres pare-feu de démonstration. Ce pare-feu permet de désactiver la
sécurité sur certaines URL, on en reparle plus loin.

Section access_control

security:

access_control:

#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }


Comme on l'a vu, le contrôle d'accès (ou access control en anglais) va s'occuper de
déterminer si le visiteur a les bons droits (rôles) pour accéder à la ressource
demandée. Il y a différents moyens d'utiliser les contrôles d'accès :

 Soit ici depuis la configuration, en appliquant des règles sur des URL. On
sécurise ainsi un ensemble d'URL en une seule ligne, par exemple toutes celles
qui commencent par/admin.
 Soit directement dans les contrôleurs, en appliquant des règles sur les
méthodes des contrôleurs. On peut ainsi appliquer des règles différentes selon
des paramètres, vous êtes très libres.
Ces deux moyens d'utiliser la même protection par rôle sont très complémentaires, et
offrent une flexibilité intéressante, on en reparle.

Mettre en place un pare-feu


Maintenant que nous avons survolé le fichier de configuration, vous avez une vue
d'ensemble rapide de ce qu'il est possible de configurer. Parfait !

Il est temps de passer aux choses sérieuses, en mettant en place une authentification
pour notre application. Nous allons le faire en deux étapes. La première est la
construction d'un pare-feu, la deuxième est la construction d'un formulaire de
connexion. Commençons.
1. Créer le pare-feu

Commençons par créer un pare-feu simple, que nous appellerons main, comme ceci :

# app/config/[Link]

security:

firewalls:

dev:

pattern: ^/(_(profiler|wdt)|css|images|js)/

security: false

main:

pattern: ^/

anonymous: true
Dans les trois petites lignes que nous venons de rajouter :

 Main est le nom du pare-feu. Il s'agit juste d'un identifiant unique, mettez en
réalité ce que vous voulez.
 Pattern : ^/est un masque d'URL. Cela signifie que toutes les URL
commençant par « / » (c'est-à-dire notre site tout entier) sont protégées par ce
pare-feu. On dit qu'elles sont derrière le pare-feu main.
 Anonymous : true accepte les utilisateurs anonymes. Nous protégerons nos
ressources grâce aux rôles.
Le pare-feu main recoupe les URL du pare-feu dev, c'est vrai. En fait, seul un unique
pare-feu peut agir sur une URL, et la règle d'attribution est la même que pour les routes
: premier arrivé, premier servi ! En l'occurrence, le pare-feu dev est défini avant notre
pare-feu main, donc une URL/css/…sera protégée par le pare-feu dev (car elle
correspond à son pattern). Ce pare-feu désactive totalement la sécurité, au final les
URL/css/…ne sont pas protégées du tout.
Si vous actualisez n'importe quelle page de votre site, vous pouvez maintenant voir
dans la barre d'outils en bas que vous êtes authentifié en tant qu'anonyme, comme
sur la figure suivante.
Je suis authentifié en tant qu'anonyme

Authentifié en tant qu'anonyme ? Ce n’est pas un peu bizarre ça ?

Hé, hé ! En effet. En fait, les utilisateurs anonymes sont techniquement authentifiés.


Mais ils restent des anonymes, et si nous mettions la valeur du paramètre anonymous
à false dans la configuration, on se ferait bien refuser l'accès. Pour distinguer les
anonymes authentifiés des vrais membres authentifiés, il faudra jouer sur les rôles, on
en reparle plus loin, ne vous inquiétez pas.

Bon, votre pare-feu est maintenant créé, mais bien sûr il n'est pas complet, il manque
un élément indispensable pour le faire fonctionner : la méthode d'authentification. En
effet, votre pare-feu veut bien protéger vos URL, mais il faut lui dire comment vérifier
que vos visiteurs sont bien identifiés ! Et notamment, où trouver vos utilisateurs !

Définir une méthode d'authentification pour le pare-feu

Nous allons faire simple pour la méthode d'authentification : un bon vieux formulaire
HTML. Pour configurer cela, c'est l'option form_login qu'il faut rajouter à notre pare-
feu :

# app/config/[Link]

security:

firewalls:

# ...

main:

pattern: ^/

anonymous: true

provider: in_memory

form_login:

login_path: login
check_path: login_check

logout:

path: logout

target: /platform
Expliquons-les quelques nouvelles lignes :

 Provider : in_memory est le fournisseur d'utilisateurs pour ce pare-feu.


Comme je vous l'ai mentionné précédemment, un pare-feu a besoin de savoir
où trouver ses utilisateurs, cela se fait par le biais de ce paramètre. La valeur
in_memory correspond au fournisseur défini dans la section providers qu'on a
vue plus haut.
 Form_login est la méthode d'authentification utilisée pour ce pare-feu. Elle
correspond à la méthode classique, via un formulaire HTML. Ses options sont
les suivantes :
o Login_path : login correspond à la route du formulaire de connexion.
En effet, ce formulaire est bien disponible à une certaine adresse, il s'agit
ici de la route login, que nous définirons juste après.
o Check_path : login_check correspond à la route de validation du
formulaire de connexion, c'est sur cette route que seront vérifiés les
identifiants renseignés par l'utilisateur sur le formulaire précédent.
 Logout rend possible la déconnexion. En effet, par défaut il est impossible de
se déconnecter une fois authentifié. Ses options sont les suivantes :
o Path est le nom de la route à laquelle le visiteur doit aller pour être
déconnecté. On va la définir plus loin.
o Target est l'URL vers laquelle sera redirigé le visiteur après sa
déconnexion.
Je vous dois plus d'explications. Rappelez-vous, le processus est le suivant : lorsque
le système de sécurité (ici, le pare-feu) initie le processus d'authentification, il va
rediriger l'utilisateur sur le formulaire de connexion (la route login). On va créer ce
formulaire juste après, il devra envoyer les valeurs vers la route (ici, login_check) qui
va prendre en charge la soumission du formulaire.

Nous nous occupons de l'affichage du formulaire, mais c'est le système de sécurité de


Symfony2 qui va s'occuper de la soumission de ce formulaire. Concrètement, nous
allons définir un contrôleur à exécuter pour la route login, mais pas pour la route
login_check ! Symfony2 va attraper la requête de notre visiteur sur la route
login_check, et gérer lui-même l'authentification. En cas de succès, le visiteur sera
authentifié. En cas d'échec, Symfony2 le renverra vers notre formulaire de connexion
pour qu'il réessaie.

Voici alors les trois routes à définir dans le fichier [Link] :

# app/config/[Link]
# ...

login:

pattern: /login

defaults: { _controller: OCUserBundle:Security:login }

login_check:

pattern: /login_check

logout:

pattern : /logout
Comme vous pouvez le voir, on ne définit pas de contrôleur pour les routes
login_check et logout. Symfony2 va attraper tout seul les requêtes sur ces routes
(grâce au gestionnaire d'évènements, nous voyons cela dans un prochain chapitre).

Créer le bundle OCUserBundle

Ce paragraphe n'est applicable que si vous ne disposez pas déjà d'un bundle
UserBundle.
Cela ne vous a pas échappé, j'ai défini le contrôleur à exécuter sur la route login
comme étant dans le bundle OCUserBundle. En effet, la gestion des utilisateurs sur un
site mérite amplement son propre bundle !

Je vous laisse générer ce bundle à l'aide de la commande suivante qu'on a déjà


abordée :
php app/console generate: bundle
Si vous mettez yes pour importer automatiquement les routes du bundle généré, la
commande va vous retourner ce message :
Importing the bundle routing resource: FAILED

The command was not able to configure everything automatically.


You must do the following changes manually.

Bundle OCUserBundle is already imported.


C'est parce qu'elle a détecté qu'on utilisait déjà une route vers ce bundle (celle qu'on
vient de créer !), du coup elle n'a pas osé réimporter les routes du bundle. C'est de
toute façon totalement inutile, pour l'instant on n'a pas de route dans ce bundle.
Avant de continuer, je vous propose un petit nettoyage dans ce nouveau
OCUserBundle, car le générateur a tendance à trop en faire. Vous pouvez donc
supprimer allègrement :

 Le contrôleur Controller/[Link];
 Son répertoire de tests Tests/Controller;
 Son répertoire de vuesResources/views/Default;
 Le fichier de routes Resources/config/[Link] ;
 La ligne d'import du fichier de routes dans le fichier app/config/[Link].
Créer le formulaire de connexion

Il s'agit maintenant de créer le formulaire de connexion, disponible sur la route login,


soit l'URL/login. Commençons par le contrôleur :

<?php

// src/OC/UserBundle/Controller/[Link];

namespace OC\UserBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\Security\Core\SecurityContext;

class SecurityController extends Controller

public function loginAction (Request $request)

// Si le visiteur est déjà identifié, on le redirige vers l'accueil

if ($this->get('[Link]')->isGranted('IS_AUTHENTICATED_REMEMBERED')) {

return $this->redirectToRoute('oc_platform_accueil');

}
// Le service authentication_utils permet de récupérer le nom d'utilisateur

// et l'erreur dans le cas où le formulaire a déjà été soumis mais était invalide

// (mauvais mot de passe par exemple)

$authenticationUtils = $this->get('security.authentication_utils');

return $this->render ('OCUserBundle:Security:[Link]', array(

'last_username' => $authenticationUtils->getLastUsername (),

'error' => $authenticationUtils->getLastAuthenticationError (),

));

}
Ne vous laissez pas impressionner par le contrôleur, de toute façon vous n'avez pas
à le modifier pour le moment. En réalité, il ne fait qu'afficher la vue du formulaire. Le
code au milieu n'est là que pour récupérer les erreurs d'une éventuelle soumission
précédente du formulaire. Rappelez-vous : c'est Symfony2 qui gère la soumission, et
lorsqu'il y a une erreur dans l'identification, il redirige le visiteur vers ce contrôleur, en
nous donnant heureusement l'erreur pour qu'on puisse lui afficher.

La vue pourrait être la suivante :

{# src/OC/UserBundle/Resources/views/Security/[Link] #}

{% extends "OCCoreBundle::[Link]" %}

{% block body %}

{# S'il y a une erreur, on l'affiche dans un joli cadre #}

{% if error %}
<div class="alert alert-danger">{{ [Link] }}</div>

{% endif %}

{# Le formulaire, avec URL de soumission vers la route « login_check » comme on l'a vu #}

<form action="{{ path ('login_check') }}" method="post">

<label for="username">Login :</label>

<input type="text" id="username" name="_username" value="{{ last_username }}" />

<label for="password">Mot de passe :</label>

<input type="password" id="password" name="_password" />

<br />

<input type="submit" value="Connexion" />

</form>

{% endblock %}
La figure suivante montre le rendu du formulaire, accessible à l'adresse /login.

Le formulaire de connexion

Lorsque j'entre de faux identifiants, l'erreur générée est celle visible à la figure
suivante.
Mauvais identifiants

Enfin, lorsque j'entre les bons identifiants, la barre d'outils sur la page suivante
m'indique bien que je suis authentifié en tant qu'utilisateur « user », comme le montre
la figure suivante.

Je suis bien authentifié

Mais quels sont les bons identifiants ?

Il fallait lire attentivement le fichier de configuration qu'on a parcouru précédemment.


Rappelez-vous, on a défini le fournisseur d'utilisateur de notre pare-feu à in_memory,
qui est défini quelques lignes plus haut dans le fichier de configuration. Ce fournisseur
est particulier, dans le sens où il lit les utilisateurs directement dans cette configuration.
On a donc deux utilisateurs possibles : « user » et « admin », avec pour mot de passe
respectivement « userpass » et « adminpass ».

Voilà, notre formulaire de connexion est maintenant opérationnel. Vous trouverez plus
d'informations pour le personnaliser dans la documentation.

Les erreurs courantes


Il y a quelques pièges à connaître quand vous travaillerez plus avec la sécurité, en
voici quelques-uns.

Ne pas oublier la définition des routes

Une erreur bête est d'oublier de créer les routes login, login_check et logout. Ce
sont des routes obligatoires, et si vous les oubliez vous risquez de tomber sur des
erreurs 404 au milieu de votre processus d'authentification.

Les pare-feu ne partagent pas


Si vous utilisez plusieurs pare-feu, sachez qu'ils ne partagent rien les uns avec les
autres. Ainsi, si vous êtes authentifiés sur l'un, vous ne le serez pas forcément sur
l'autre, et inversement. Cela permet d’accroître la sécurité lors d'un paramétrage
complexe.

Bien mettre/login_check derrière le pare-feu

Vous devez vous assurer que l'URL du check_path (ici, /login_check) est bien
derrière le pare-feu que vous utilisez pour le formulaire de connexion (ici, main). En
effet, c'est la route qui permet l'authentification au pare-feu. Or, comme les pare-feu
ne partagent rien, si cette route n'appartient pas au pare-feu que vous voulez, vous
aurez droit à une belle erreur.

Dans notre cas, le pattern : ^/du pare-feu main prend bien l'URL /login_check, c'est
donc OK.

Ne pas sécuriser le formulaire de connexion


En effet, si le formulaire est sécurisé, comment les nouveaux arrivants vont-ils pouvoir
s'authentifier ? En l'occurrence, il faut faire attention que la page /login ne requière
aucun rôle, on fera attention à cela lorsqu'on va définir les autorisations.

Cette erreur est vicieuse, car si vous sécurisez à tort l'URL /login, vous subirez une
redirection infinie. En effet, Symfony2 considère que vous n'avez pas accès à /login,
il vous redirige donc vers le formulaire pour vous authentifier, or il s'agit de la
page/login, or vous n'avez pas accès à/login, etc.
De plus, si vous souhaitez interdire les anonymes sur le pare-feu main, le problème se
pose également, car un nouvel arrivant sera forcément anonyme et ne pourra pas
accéder au formulaire de connexion. L'idée dans ce cas est de sortir le formulaire de
connexion (la page/login) du pare-feu main. En effet, c'est le check_path qui doit
obligatoirement appartenir au pare-feu, pas le formulaire en lui-même. Si vous
souhaitez interdire les anonymes sur votre site (et uniquement dans ce cas), vous
pouvez donc vous en sortir avec la configuration suivante :

# app/config/[Link]

# ...

firewalls:

# On crée un pare-feu uniquement pour le formulaire

main_login :

# Cette expression régulière permet de prendre /login (mais pas /login_check !)

Pattern : ^/login$
anonymous: true # On autorise alors les anonymes sur ce pare-feu

main:

pattern: ^/

anonymous: false

# ...
En plaçant ce nouveau pare-feu avant notre pare-feu main, on sort le formulaire de
connexion du pare-feu sécurisé. Nos nouveaux arrivants auront donc une chance de
s'identifier !

Récupérer l'utilisateur courant


Pour récupérer les informations sur l'utilisateur courant, qu'il soit anonyme ou non, il
faut utiliser le service [Link].

Ce service dispose d'une méthode getToken (), qui permet de récupérer la session
de sécurité courante (à ne pas confondre avec la session classique, disponible elle via
$request->getSession ()). Ce token vaut null si vous êtes hors d'un pare-feu. Et si
vous êtes derrière un pare-feu, alors vous pouvez récupérer l'utilisateur courant grâce
à $token->getUser ().

Depuis le contrôleur ou un service


Voici concrètement comment l'utiliser :

<?php

// On récupère le service

$security = $container->get('[Link]');

// On récupère le token

$token = $security->getToken ();

// Si la requête courante n'est pas derrière un pare-feu, $token est null

// Sinon, on récupère l'utilisateur


$user = $token->getUser ();

// Si l'utilisateur courant est anonyme, $user vaut « anon. »

// Sinon, c'est une instance de notre entité User, on peut l'utiliser normalement

$user->getUsername () ;
Comme vous pouvez le voir, il y a pas mal de vérifications à faire, suivant les différents
cas possibles. Heureusement, en pratique, le contrôleur dispose d'un raccourci
permettant d'automatiser cela, il s'agit de la méthode $this->getUser(). Cette
méthode retourne :

 si la requête n'est pas derrière un pare-feu, ou si l'utilisateur courant est


Null
anonyme ;
 Une instance de User le reste du temps (utilisateur authentifié derrière un pare-
feu et non-anonyme).
Du coup, voici le code simplifié depuis un contrôleur :

<?php

// Depuis un contrôleur

$user = $this->getUser () ;

if (null === $user) {

// Ici, l'utilisateur est anonyme ou l'URL n'est pas derrière un pare-feu

} else {

// Ici, $user est une instance de notre classe User

}
Depuis une vue Twig
Vous avez accès plus facilement à l'utilisateur directement depuis Twig. Vous savez
que Twig dispose de quelques variables globales via la variable {{ app }} ; eh bien,
l'utilisateur courant en fait partie, via {{ [Link] }}:

Bonjour {{ [Link] }} - {{ [Link] }}


Au même titre que dans un contrôleur, attention à ne pas utiliser {{ [Link] }}
lorsque l'utilisateur n'est pas authentifié, car il vaut null.

Gestion des autorisations avec les rôles


La section précédente nous a amenés à réaliser une authentification opérationnelle.
Vous avez un pare-feu, une méthode d'authentification par formulaire HTML, et deux
utilisateurs. La couche authentification est complète !

Dans cette section, nous allons nous occuper de la deuxième couche de la sécurité :
l'autorisation. C'est une phase bien plus simple à gérer heureusement, il suffit juste
de demander tel(s) droit(s) à l'utilisateur courant (identifié ou non).

Définition des rôles

Rappelez-vous, on a croisé les rôles dans le fichier [Link]. La notion de rôle et


autorisation est très simple : pour limiter l'accès à certaines pages, on va se baser sur
les rôles de l'utilisateur. Ainsi, limiter l'accès au panel d'administration revient à limiter
cet accès aux utilisateurs disposant du rôle ROLE_ADMIN (par exemple).

Tout d'abord, essayons d'imaginer les rôles dont on aura besoin dans notre application
de plateforme d'annonce. Je pense à :

 ROLE_AUTEUR : pour ceux qui ont le droit d'écrire des annonces ;


 ROLE_MODERATEUR : pour ceux qui peuvent modérer les annonces ;
 ROLE_ADMIN : pour ceux qui peuvent tout faire.
Maintenant l'idée est de créer une hiérarchie entre ces rôles. On va dire que les auteurs
et les modérateurs sont bien différents, et que les admins ont les droits cumulés des
auteurs et des modérateurs. Ainsi, pour limiter l'accès à certaines pages, on ne va pas
faire « si l'utilisateur a ROLE_AUTEUR ou s'il a ROLE_ADMIN, alors il peut écrire une
annonce ». Grâce à la définition de la hiérarchie, on peut faire simplement « si
l'utilisateur a ROLE_AUTEUR ». Car un utilisateur qui dispose de ROLE_ADMIN dispose
également de ROLE_AUTEUR, c'est une inclusion.

Ce sont ces relations, et uniquement ces relations, que nous allons inscrire dans le
fichier [Link]. Voici donc comment décrire dans la configuration la hiérarchie
qu'on vient de définir :

# app/config/[Link]

security:

role_hierarchy :

# Un admin hérite des droits d'auteur et de modérateur

ROLE_ADMIN : [ROLE_AUTEUR, ROLE_MODERATEUR]


# On garde ce rôle superadmin, il nous resservira par la suite

ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]


Remarquez que je n'ai pas utilisé le rôle ROLE_USER, qui n'est pas toujours utile. Avec
cette hiérarchie, voici des exemples de tests que l'on peut faire :

 Si l'utilisateur a le rôle ROLE_AUTEUR, alors il peut écrire une annonce. Les


auteurs et les admins peuvent donc le faire.
 Si l'utilisateur a le rôle ROLE_ADMIN, alors il peut supprimer une annonce. Seuls
les admins peuvent donc le faire.
Tous ces tests nous permettront de limiter l'accès à nos différentes pages.

J'insiste sur le fait qu'on définit ici uniquement la hiérarchie entre les rôles, et non
l'exhaustivité des rôles. Ainsi, on pourrait tout à fait avoir un rôle ROLE_TRUC dans notre
application, mais que les administrateurs n'héritent pas.
Tester les rôles de l'utilisateur
Il est temps maintenant de tester concrètement si l'utilisateur courant dispose de tel ou
tel rôle. Cela nous permettra de lui donner accès à la page, de lui afficher ou non un
certain lien, etc. Laissez libre cours à votre imagination.

Il existe quatre méthodes pour faire ce test : les annotations, le service


[Link], Twig, et les contrôles d'accès. Ce sont quatre façons de
faire exactement la même chose.

Utiliser directement le service [Link]

Ce n'est pas le moyen le plus court, mais c'est celui par lequel passent les trois autres
méthodes. Il faut donc que je vous en parle en premier !

Depuis votre contrôleur ou n'importe quel autre service, il vous faut accéder au service
[Link] et appeler la méthode isGranted, tout simplement. Par exemple
dans notre contrôleur :

<?php

// src/OC/PlatformBundle/Controller/[Link]

namespace OC\PlatformBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class AdvertController extends Controller

public function addAction(Request $request)

// On vérifie que l'utilisateur dispose bien du rôle ROLE_AUTEUR

if (!$this->get('[Link]')->isGranted('ROLE_AUTEUR')) {

// Sinon on déclenche une exception « Accès interdit »

throw new AccessDeniedException('Accès limité aux auteurs.');

// Ici l'utilisateur a les droits suffisant,

// on peut ajouter une annonce

}
C'est tout ! Vous pouvez aller sur /platform, mais impossible d'atteindre la page d'ajout
d'une annonce sur /platform/add, car vous ne disposez pas (encore !) du rôle
ROLE_AUTEUR, comme le montre la figure suivante.

L'accès est
interdit
Utiliser les annotations dans un contrôleur

Pour faire exactement ce qu'on vient de faire avec le service [Link], il


existe un moyen bien plus rapide et joli : les annotations !

L'annotation @Security que nous allons utiliser ici provient du bundle


SensioFrameworkExtraBundle,c'est un bundle qui apporte quelques petits plus au
framework. Pas besoin d'explication, son utilisation basique est assez simple
; regardez le code :

<?php

// src/OC/PlatformBundle/Controller/[Link]

namespace OC\PlatformBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

// N'oubliez pas ce use pour l'annotation

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

class AdvertController extends Controller

/**

* @Security("has_role('ROLE_AUTEUR')")

*/

public function addAction(Request $request)

// Plus besoin du if avec le [Link], l'annotation s'occupe de tout !

// Dans cette méthode, vous êtes sûrs que l'utilisateur courant dispose du rôle
ROLE_AUTEUR
}

}
Et voilà ! Grâce à l'annotation @Security, on a sécurisé notre méthode en une seule
ligne, vraiment pratique.

La valeur de l'option par défaut de l'annotation est en fait une expression, dans laquelle
vous pouvez utiliser plusieurs variables et fonctions (dont has_role qu'on a utilisé ici).
Si vous voulez vérifier que l'utilisateur a deux rôles, vous pouvez faire comme ceci :

<?php

/**

* @Security("has_role('ROLE_AUTEUR') and has_role('ROLE_AUTRE')")

* /
Le détail des variables et fonctions disponibles est dans la documentation.

Pour vérifier simplement que l'utilisateur est authentifié, et donc qu'il n'est pas
anonyme, vous pouvez utiliser le rôle spécial IS_AUTHENTICATED_REMEMBERED.

Depuis une vue Twig


Cette méthode est très pratique pour afficher du contenu différent selon les rôles de
vos utilisateurs. Typiquement, le lien pour ajouter une annonce ne doit être visible que
pour les membres qui disposent du rôle ROLE_AUTEUR (car c'est la contrainte que nous
avons mise sur la méthode addAction ()).

Pour cela, Twig dispose d'une fonction is_granted () qui est en réalité un raccourci
pour exécuter la méthode isGranted () du service [Link]. La voici en
application :

{# On n'affiche le lien « Ajouter une annonce » qu'aux auteurs

(Et admins, qui héritent du rôle auteur) #}

{% if is_granted('ROLE_AUTEUR') %}

<li><a href="{{ path('oc_platform_add') }}">Ajouter une annonce</a></li>

{% endif %}
Utiliser les contrôles d'accès
La méthode de l'annotation permet de sécuriser une méthode de contrôleur. La
méthode avec Twig permet de sécuriser l'affichage. La méthode des contrôles d'accès
permet de sécuriser des URL. Elle se configure dans le fichier de configuration de la
sécurité, c'est la dernière section. Voici par exemple comment sécuriser tout un panel
d'administration (des pages dont l'URL commence par/admin) en une seule ligne :

# app/config/[Link]
security:

access_control:

- { path: ^/admin, roles: ROLE_ADMIN }


Ainsi, toutes les URL qui correspondent aupath(ici, toutes celles qui commencent
par/admin) requièrent le rôleROLE_ADMIN.

C'est une méthode complémentaire des autres. Elle permet également de sécuriser
vos URL par IP ou par canal (http ou https), grâce à des options :

# app/config/[Link]

security:

access_control:

- { path: ^/admin, ip: [Link], requires_channel: https }


Pour conclure sur les méthodes de sécurisation
Symfony2 offre plusieurs moyens de sécuriser vos ressources (méthode de contrôleur,
affichage, URL). N'hésitez pas à vous servir de la méthode la plus appropriée pour
chacun de vos besoins. C'est la complémentarité des méthodes qui fait l'efficacité de
la sécurité avec Symfony2.

Pour tester les sécurités qu'on met en place, n'hésitez pas à charger vos pages avec
les deux utilisateurs « user » et « admin ». L'utilisateur admin ayant le rôle ROLE_ADMIN,
il a les droits pour ajouter une annonce et voir le lien d'ajout. Pour vous déconnecter
d'un utilisateur, allez sur /logout.

Utiliser des utilisateurs de la base de données


Pour l'instant, nous n'avons fait qu'utiliser les deux pauvres utilisateurs définis dans le
fichier de configuration. C'était pratique pour faire nos premiers tests, car ils ne
nécessitent aucun paramétrage particulier. Mais maintenant, passons à la vitesse
supérieure et enregistrons nos utilisateurs en base de données !

Qui sont les utilisateurs ?


Dans Symfony2, un utilisateur est un objet qui implémente l'interface UserInterface,
c'est tout. N'hésitez pas à aller voir à quoi ressemble cette interface, il n'y a en fait que
cinq méthodes obligatoires, ce n'est pas grand-chose.

Heureusement il existe également une classe User qui implémente cette interface. Les
utilisateurs que nous avons actuellement sont des instances de cette classe.
Créons notre classe d'utilisateurs

En vue d'enregistrer nos utilisateurs en base de données, il nous faut créer notre
propre classe utilisateur, qui sera également une entité pour être persistée. Je vous
invite donc à générer directement une entité User au sein du bundle OCUserBundle,
grâce au générateur de Doctrine (php app/console doctrine:generate:entity), avec
les attributs minimum suivants (tirés de l'interface) :

 Username : c'est l'identifiant de l'utilisateur au sein de la couche sécurité. Cela


ne nous empêchera pas d'utiliser également un id numérique pour notre entité,
c'est plus simple pour nous ;
 Password : le mot de passe ;
 Salt : le sel, pour encoder le mot de passe, on en reparle plus loin ;
 Roles : un tableau (attention à bien le définir comme tel lors de la génération)
contenant les rôles de l'utilisateur.
Voici la classe que j'obtiens :

<?php

namespace OC\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**

* @ORM\Entity(repositoryClass="OC\UserBundle\Entity\UserRepository")

*/

class User

/**

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/
private $id;

/**

* @ORM\Column(name="username", type="string", length=255, unique=true)

*/

private $username;

/**

* @ORM\Column(name="password", type="string", length=255)

*/

private $password;

/**

* @ORM\Column(name="salt", type="string", length=255)

*/

private $salt;

/**

* @ORM\Column(name="roles", type="array")

*/

private $roles = array();

// Les getters et setters


public function eraseCredentials ()

}
J'ai défini une valeur par défaut (array ()) à l'attribut $roles. J'ai également défini
l'attribut username comme étant unique, car c'est l'identifiant qu'utilise la couche
sécurité, il est donc obligatoire qu'il soit unique. Enfin, j'ai ajouté la méthode
eraseCredentials (), vide pour l'instant mais obligatoire de par l'interface suivante.
Et pour que Symfony2 l'accepte comme classe utilisateur de la couche sécurité, il faut
qu'on implémente l'interface UserInterface :

<?php

// src/OC/UserBundle/Entity/[Link]

use Symfony\Component\Security\Core\User\UserInterface;

class User implements UserInterface

// …

}
Et voilà, nous avons une classe prête à être utilisée !

Et bien sûr, exécutez un petitphp app/console doctrine:schema:updatepour mettre à


jour la base de données avec cette nouvelle entité.
Créons quelques utilisateurs de test

Pour s'amuser avec notre nouvelle entitéUser, il faut créer quelques instances dans la
base de données. Réutilisons ici les fixtures, voici ce que je vous propose :

<?php

// src/OC/UserBundle/DataFixtures/ORM/[Link]

namespace OC\UserBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;

use Doctrine\Common\Persistence\ObjectManager;

use OC\UserBundle\Entity\User;

class LoadUser implements FixtureInterface

public function load (ObjectManager $manager)

// Les noms d'utilisateurs à créer

$listNames = array ('Alexandre', 'Marine', 'Anna');

foreach ($listNames as $name) {

// On crée l'utilisateur

$user = new User;

// Le nom d'utilisateur et le mot de passe sont identiques

$user->setUsername($name);

$user->setPassword($name);

// On ne se sert pas du sel pour l'instant

$user->setSalt('');

// On définit uniquement le role ROLE_USER qui est le role de base

$user->setRoles(array('ROLE_USER'));
// On le persiste

$manager->persist($user) ;

// On déclenche l'enregistrement

$manager->flush () ;

}
Exécutez cette fois la commande :
php app/console doctrine:fixtures:load
Et voilà, nous avons maintenant trois utilisateurs dans la base de données.

Définissons l'encodeur pour notre nouvelle classe d'utilisateurs


Ce n'est pas un piège mais presque, rappelez-vous l'encodeur défini pour nos
précédents utilisateurs spécifiait la classe User utilisée. Or maintenant nous allons
nous servir d'une autre classe, il s'agit de OC\UserBundle\Entity\User. Il est donc
obligatoire de définir quel encodeur utiliser pour notre nouvelle classe. Comme nous
avons mis les mots de passe en clair dans les fixtures, nous devons également utiliser
l'encodeur plaintext, qui n'encode pas les mots de passe mais les laisse en clair,
c'est plus simple pour nos tests.

Ajoutez donc cet encodeur dans la configuration, juste en dessous de celui existant :

# app/config/[Link]

security:

encoders:

Symfony\Component\Security\Core\User\User: plaintext

OC\UserBundle\Entity\User: plaintext
Définissons le fournisseur d'utilisateurs
On en a parlé plus haut, il faut définir un fournisseur (provider) pour que le pare-feu
puisse identifier et récupérer les utilisateurs.
Qu'est-ce qu'un fournisseur d'utilisateurs, concrètement ?

Un fournisseur
d'utilisateurs est une classe qui implémente l'interface
UserProviderInterface, qui contient juste trois méthodes :

 loadUserByUsername ($username), qui charge un utilisateur à partir d'un nom


d'utilisateur ;
 refreshUser ($user),
qui rafraîchit un utilisateur avec les valeurs d'origine ;
 supportsClass (), qui détermine quelle classe d'utilisateurs gère le fournisseur.
Vous pouvez le constater, un fournisseur ne fait pas grand-chose, à part charger ou
rafraîchir les utilisateurs.

Symfony2 dispose déjà de trois types de fournisseurs, qui implémentent tous l'interface
précédente évidemment, les voici :

 memory utilise les utilisateurs définis dans la configuration, c'est celui qu'on a
utilisé jusqu'à maintenant ;
 entity utilise de façon simple une entité pour fournir les utilisateurs, c'est celui
qu'on va utiliser ;
 Id permet d'utiliser un service quelconque en tant que fournisseur, en précisant
le nom du service.
Créer notre fournisseur entity

Il est temps de créer le fournisseur entity pour notre entité User. Celui-ci existe déjà
dans Symfony2, nous n'avons donc pas de code à faire, juste un peu de configuration.
On va l'appeler « main », un nom arbitraire. Voici comment le déclarer :

# app/config/[Link]

security:

providers:

# … vous pouvez supprimer le fournisseur « in_memory »

# Et voici notre nouveau fournisseur :

main:

entity:

class: OC\UserBundle\Entity\User

property: username
Il y a deux paramètres à préciser pour le fournisseur :
 La classe à utiliser évidemment, il s'agit pour le fournisseur de savoir quel
repository Doctrine récupérer pour ensuite charger nos entités. Vous pouvez
également utiliser le nom logique de l'entité, ici OCUserBundle:User ;
 L'attribut de la classe qui sert d'identifiant, on utilise username, donc on le lui dit.
Dans la configuration, faites bien la différence entre "main" et "entity" :
 "main" est le nom du provider, totalement arbitraire. On aurait pu utiliser
"mon_super_provider", attention juste à bien utiliser le même dans la
configuration du pare-feu.
 "entity" est le type de provider, c'est un nom fixe, définit dans symfony.
Dire au pare-feu d'utiliser le nouveau fournisseur
Maintenant que notre fournisseur existe, il faut demander au pare-feu de l'utiliser lui,
et non l'ancien fournisseur in_memory. Pour cela, modifions simplement la valeur du
paramètre provider, comme ceci :

# app/config/[Link]

security:

firewalls:

main:

pattern: ^/

anonymous: true

provider: main # On change cette valeur

# … reste de la configuration du pare-feu


Vous trouverez encore plus d'informations sur ce type de fournisseur dans la
documentation.
Manipuler vos utilisateurs
La couche sécurité est maintenant pleinement opérationnelle et utilise des utilisateurs
stockés en base de données. Testez-la dès maintenant en vous identifiant avec le nom
d'utilisateur et mot de passe définis dans le fichier de fixtures. C'est parfait !

Vous voulez faire un formulaire d'inscription ? Modifier vos utilisateurs ? Changer


leurs rôles ?

Je pourrais vous expliquer comment le faire, mais en réalité vous savez déjà le faire !

L'entité User que nous avons créée est une entité tout à fait comme les autres. À ce
stade du cours vous savez ajouter, modifier et supprimer des annonces, alors il en va
de même pour cette nouvelle entité qui représente vos utilisateurs.
Bref, faites-vous confiance, vous avez toutes les clés en main pour manipuler
entièrement vos utilisateurs.

Cependant, toutes les pages d'un espace membres sont assez classiques : inscription,
mot de passe perdu, modification du profil, etc. Tout cela est du déjà-vu. Et si c'est
déjà vu, il existe déjà certainement un bundle pour cela. Et je vous le confirme, il existe
même un excellent bundle, il s'agit de FOSUserBundle et je vous propose de l'installer
!

Utiliser FOSUserBundle
Comme vous avez pu le voir, la sécurité fait intervenir de nombreux acteurs et
demande pas mal de travail de mise en place. C'est normal, c'est un point sensible
d'un site internet. Heureusement, d'autres développeurs talentueux ont réussi à nous
faciliter la tâche en créant un bundle qui gère une partie de la sécurité !

Ce bundle s'appelle FOSUserBundle, il est très utilisé par la communauté Symfony2 car
vraiment bien fait, et surtout répondant à un besoin vraiment basique d'un site Internet
: l'authentification des membres.

Je vous propose donc d'installer ce bundle dans la suite de cette section. Cela n'est
en rien obligatoire, vous pouvez tout à fait continuer avec le User qu'on vient de
développer, cela fonctionne tout aussi bien !

Installation de FOSUserBundle
Télécharger le bundle

Le bundle FOSUserBundle est hébergé sur GitHub, comme beaucoup de bundles et


projets Symfony2. Sa page est ici
:[Link]

Mais pour ajouter ce bundle, vous l'avez compris, il faut utiliser Composer !
Commencez par déclarer cette nouvelle dépendance dans votre fichier [Link]:

// [Link]

// …

"require": {

// …

"friendsofsymfony/user-bundle": "dev-master"
}

// …

}
À l'heure où j'écris ces lignes, les mainteneurs de ce bundle n'ont pas sorti de version
2 stable du bundle. La branche 1.3.* est un peu vieille, je vous conseille donc d'utiliser
directement la branche master via dev-master.
Ensuite, il faut dire à Composer d'installer cette nouvelle dépendance :
php [Link] update friendsofsymfony/user-bundle
L'argument après la commande update permet de dire à Composer de ne mettre à
jour que cette dépendance. Ici, cela permet de ne mettre à jour que FOSUserBundle, et
pas les autres dépendances. C'est plus rapide, mais si vous vouliez tout mettre à jour,
supprimez simplement ce paramètre.
Activer le bundle
Si vos souvenirs sont bons, vous devriez savoir qu'un bundle ne s'active pas tout seul,
il faut aller l'enregistrer dans le noyau de Symfony2. Pour cela, ouvrez le fichier
app/[Link] pour enregistrer le bundle :

<?php

// app/[Link]

public function registerBundles()

$bundles = array(

// …

new FOS\UserBundle\FOSUserBundle(),

);

}
C'est bon, le bundle est bien enregistré. Mais inutile d'essayer d'accéder à votre
application Symfony2 maintenant, elle ne marchera pas. Il faut en effet faire un peu de
configuration et de personnalisation avant de pouvoir tout remettre en marche.

Hériter FOSUserBundle depuis notre OCUserBundle

FOSUserBundle est un bundle générique évidemment, car il doit pouvoir s'adapter à


tout type d'utilisateur de n'importe quel site internet. Vous imaginez bien que, du coup,
ce n'est pas un bundle prêt à l'emploi directement après son installation ! Il faut donc
s'atteler à le personnaliser afin de faire correspondre le bundle à nos besoins. Cette
personnalisation passe par l'héritage de bundle.

C'est une fonctionnalité intéressante qui va nous permettre de personnaliser


facilement et proprement le bundle que l'on vient d'installer. L'héritage de bundle est
même très simple à réaliser. Prenez le fichier [Link] qui représente notre
bundle, et modifiez-le comme suit :

<?php

// src/OC/UserBundle/[Link]

namespace OC\UserBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class OCUserBundle extends Bundle

public function getParent()

return 'FOSUserBundle';

}
Et c'est tout ! On a juste rajouté cette méthode getParent (), et Symfony2 va savoir
gérer le reste.

Lorsqu'un bundle A (notre OCUserBundle) hérite d'un bundle B (FOSUserBundle),


cela signifie entre autre que :

 Si une vue du bundle A a le même nom qu'une vue du bundle B, c'est la vue du
bundle A qui sera utilisée lorsque vous faites "BundleB::[Link]",
alors que vous mentionnez bien "BundleB" dans le nom de la vue ;
 Si un contrôleur du bundle A a le même nom qu'un contrôleur du bundle B, c'est
le contrôleur du bundle A qui sera utilisé lorsque vous faites "BundleB :
myController : myAction", alors que vous mentionnez bien "BundleB" dans le
nom du contrôleur.
Modifier notre entité User

Bien que nous ayons déjà créé une entité User, ce nouveau bundle en contient une
plus complète, qu'on va utiliser avec plaisir plutôt que de tout recoder nous-mêmes.
On va donc hériter l'entité User de FOSUserBundle depuis notre entité User de
notre OCUserBundle. Notre entité ne contiendra que les attributs que l'on souhaite
avoir et qui ne sont pas dans celle de FOSUserBundle. En fait, notre entité ne contient
plus grand-chose au final, voici ce que cela donne :

<?php

// src/OC/UserBundle/Entity/[Link]

namespace OC\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use FOS\UserBundle\Model\User as BaseUser;

/**

* @ORM\Entity

*/

class User extends BaseUser

/**

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/

protected $id;

}
Plus besoin d'implémenter UserInterface, car on hérite de l'entité User du bundle
FOSUB, qui, elle, implémente cette interface. Pas besoin non plus d'écrire tous les
setters et getters, ils sont tous hérités, même le getter getId !
Alors c'est joli, mais pourquoi est-ce que l'on a fait cela ? En fait, le bundle
FOSUserBundle ne définit pas vraiment l'entité User, il définit une mapped superclass !
Un nom un peu barbare, juste pour dire que c'est une entité abstraite, et qu'il faut en
hériter pour en faire une vraie entité. C'est donc ce que nous venons juste de faire.

Cela permet en fait de garder la main sur notre entité. On peut ainsi lui ajouter des
attributs (selon vos besoins), en plus de ceux déjà définis. Pour information, les
attributs qui existent déjà sont :

 : nom d'utilisateur avec lequel l'utilisateur va s'identifier ;


Username
 Email : l'adresse e-mail ;
 Enabled : true ou false suivant que l'inscription de l'utilisateur a été validée
ou non (dans le cas d'une confirmation par e-mail par exemple) ;
 Password : le mot de passe de l'utilisateur ;
 LastLogin : la date de la dernière connexion ;
 Locked : si vous voulez désactiver des comptes ;
 Expired : si vous voulez que les comptes expirent au-delà d'une certaine durée.
Je vous en passe certains qui sont plus à un usage interne. Sachez tout de même que
vous pouvez tous les retrouver dans la définition de la mapped superclass. C'est un
fichier de mapping XML, l'équivalent des annotations qu'on utilise de notre côté.

Vous pouvez rajouter dès maintenant des attributs à votre entité User, comme vous
savez le faire depuis la partie Doctrine2.

Configurer le bundle
Ensuite, nous devons définir certains paramètres obligatoires au fonctionnement de
FOSUserBundle. Ouvrez votre [Link] et ajoutez la section suivante :

# app/config/[Link]

# …

fos_user:

db_driver: orm # Le type de BDD à utiliser, nous utilisons


l'ORM Doctrine depuis le début

firewall_name: main # Le nom du firewall derrière lequel on utilisera


ces utilisateurs

user_class: OC\UserBundle\Entity\User # La classe de l'entité User que nous utilisons


Et voilà, on a bien installé FOSUserBundle ! Avant d'aller plus loin, créons la table User
et ajoutons quelques membres pour les tests.

Mise à jour de la table User

Il faut maintenant mettre à jour la table des utilisateurs, vu les modifications que l'on
vient de faire. D'abord, allez la vider depuis phpMyAdmin, puis exécutez la commande
php app/console doctrine : schema : update --force. Et voilà, votre table est créée
!

On a fini d'initialiser le bundle. Bon, bien sûr pour l'instant Symfony2 ne l'utilise pas
encore, il manque un peu de configuration, attaquons-la.

Configuration de la sécurité pour utiliser le bundle


Maintenant on va reprendre notre configuration de la sécurité, pour utiliser tous les
outils fournis par le bundle dès que l'on peut. Reprenez le [Link] sous la main,
et c'est parti !

L'encodeur
Il est temps d'utiliser un vrai encodeur pour nos utilisateurs, car il est bien sûr hors de
question de stocker leur mot de passe en clair ! On utilise couramment la méthode
sha512. Modifiez donc l'encodeur de notre classe comme ceci :

# app/config/[Link]

security:

encoders:

OC\UserBundle\Entity\User: sha512
Le fournisseur

Le bundle inclut son propre fournisseur en tant que service, qui utilise notre entité User
mais avec ses propres outils. Vous pouvez donc modifier notre fournisseur main
comme suit :

# app/config/[Link]

security:

# …
providers:

main:

id: fos_user.user_provider.username
Dans cette configuration, fos_user.user_provider. username est le nom du service
fourni par le bundle FOSUB.

Le pare-feu
Notre pare-feu était déjà pleinement opérationnel. Étant donné que nous n'avons pas
changé le nom du fournisseur associé, la configuration du pare-feu est déjà à jour.
Nous n'avons donc rien à modifier ici.

On va juste en profiter pour activer la possibilité de « Se souvenir de moi » à la


connexion. Cela permet aux utilisateurs de ne pas s'authentifier manuellement à
chaque fois qu'ils accèdent à notre site. Ajoutez donc l'option remember_me dans la
configuration. Voici ce que cela donne :

# app/config/[Link]

security:

# …

firewalls:

# … le pare-feu « dev »

# Firewall principal pour le reste de notre site

main:

pattern: ^/

anonymous: true

provider: main

form_login:

login_path: login
check_path: login_check

logout:

path: logout

target: /platform

remember_me:

key: %secret% # %secret% est un paramètre de [Link]


J'ai juste ajouté le dernier paramètre remember_me.

Configuration de la sécurité : check !


Et voilà, votre site est prêt à être sécurisé ! En effet, on a fini de configurer la sécurité
pour utiliser tout ce qu'offre le bundle à ce niveau.

Pour tester à nouveau si tout fonctionne, il faut ajouter des utilisateurs à notre base de
données. Pour cela, on ne va pas réutiliser nos fixtures précédentes, mais on va
utiliser une commande très sympa proposée par FOSUserBundle. Exécutez la
commande suivante et laissez-vous guider :
php app/console fos:user:create
Vous l'aurez deviné, c'est une commande très pratique qui permet de créer des
utilisateurs facilement. Laissez-vous guider, elle vous demande le nom d'utilisateur,
l'e-mail et le mot de passe, et hop ! elle crée l'utilisateur. Vous pouvez aller vérifier le
résultat dans phpMyAdmin. Notez au passage que le mot de passe a bien été encodé,
en sha512 comme on l'a demandé.

FOSUserBundle offre bien plus que seulement de la sécurité. Du coup, maintenant que
la sécurité est bien configurée, passons au reste de la configuration du bundle.

Configuration du bundle FOSUserBundle


Configuration des routes

En plus de gérer la sécurité, le bundle FOSUserBundle gère aussi les pages classiques
comme la page de connexion, celle d'inscription, etc. Pour toutes ces pages, il faut
évidemment enregistrer les routes correspondantes. Les développeurs du bundle ont
volontairement éclaté toutes les routes dans plusieurs fichiers pour pouvoir
personnaliser facilement toutes ces pages. Pour l'instant, on veut juste les rendre
disponibles, on les personnalisera plus tard. Ajoutez donc dans votre [Link] les
imports suivants à la suite du nôtre :

# app/config/[Link]

# …
fos_user_security:

resource: "@FOSUserBundle/Resources/config/routing/[Link]"

fos_user_profile:

resource: "@FOSUserBundle/Resources/config/routing/[Link]"

prefix: /profile

fos_user_register:

resource: "@FOSUserBundle/Resources/config/routing/[Link]"

prefix: /register

fos_user_resetting:

resource: "@FOSUserBundle/Resources/config/routing/[Link]"

prefix: /resetting

fos_user_change_password:

resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"

prefix: /profile
Vous remarquez que les routes sont définies en XML et non en YML comme on en a
l'habitude dans ce cours. En effet, je vous en avais parlé tout au début, Symfony2
permet d'utiliser plusieurs méthodes pour les fichiers de configuration : YML, XML et
même PHP, au choix du développeur. Ouvrez ces fichiers de routes pour voir à quoi
ressemblent des routes en XML. C'est quand même moins lisible qu'en YML, c'est
pour cela qu'on a choisi YML au début.

Ouvrez vraiment ces fichiers pour connaître toutes les routes qu'ils contiennent. Vous
saurez ainsi faire des liens vers toutes les pages qu'offre le bundle : inscription, mot
de passe perdu, etc. Inutile de réinventer la roue ! Voici quand même un extrait de la
commande php app/console router: debug pour les routes qui concernent ce bundle
:
fos_user_security_login ANY ANY /login

fos_user_security_check ANY ANY /login_check

fos_user_security_logout ANY ANY /logout

fos_user_profile_show GET ANY /profile/

fos_user_profile_edit ANY ANY /profile/edit

fos_user_registration_register ANY ANY /register/

fos_user_registration_check_email GET ANY /register/check-email

fos_user_registration_confirm GET ANY /register/confirm/{token}

fos_user_registration_confirmed GET ANY /register/confirmed

fos_user_resetting_request GET ANY /resetting/request

fos_user_resetting_send_email POST ANY /resetting/send-email

fos_user_resetting_check_email GET ANY /resetting/check-email

fos_user_resetting_reset GET|POST ANY /resetting/reset/{token}

fos_user_change_password GET|POST ANY /profile/change-password


Vous notez que le bundle définit également les routes de sécurité /login et autres.
Du coup, je vous propose de laisser le bundle gérer cela, supprimez donc les trois
routes login, login_check et logout qu'on avait déjà définies et qui ne servent plus.
De plus, il faut adapter la configuration du pare-feu, car le nom de ces routes a changé,
voici ce que cela donne :

# app/config/[Link]

security:

firewalls:

main:

pattern: ^/

anonymous: true

provider: main
form_login:

login_path: fos_user_security_login

check_path: fos_user_security_check

logout:

path: fos_user_security_logout

target: /platform

remember_me:

key: %secret%
Comme notre bundle OCUserBundle hérite de FOSUserBundle, c'est notre contrôleur et
donc notre vue qui sont utilisés sur la route login pour l'instant, car les noms que nous
avions utilisés sont les mêmes que ceux de FOSUserBundle. Étant donné que le
contrôleur de FOSUserBundle apporte un petit plus (protection CSRF notamment), je
vous propose de supprimer notre contrôleur SecurityController et notre vue
Security/[Link] pour laisser ceux de FOSUserBundle prendre la main.
Il reste quelques petits détails à gérer comme la page de login qui n'est pas la plus
sexy, sa traduction, et aussi un bouton « Déconnexion », parce que changer
manuellement l'adresse en/logout, c'est pas super user-friendly !

Personnalisation esthétique du bundle


Heureusement tout cela est assez simple.

Attention, la personnalisation esthétique que nous allons faire ne concerne en rien la


couche sécurité à proprement parler. Soyez bien conscients de la différence !
Intégrer les pages du bundle dans notre layout

FOSUserBundle utilise un layout volontairement simpliste, parce qu'il a vocation à être


remplacé par le nôtre. Le layout actuel est le suivant
: [Link] [...] [Link]

On va donc tout simplement le remplacer par une vue Twig qui va étendre notre layout.
Pour « remplacer » le layout du bundle, on va utiliser l'un des avantages d'avoir hérité
de ce bundle dans le nôtre, en créant une vue du même nom dans notre bundle.
Créez-donc la vue [Link] suivante :

{# src/OC/UserBundle/Resources/views/[Link] #}

{# On étend notre layout #}

{% extends "OCCoreBundle::[Link]" %}
{# Dans notre layout, il faut définir le block body #}

{% block body %}

{# On affiche les messages flash que définissent les contrôleurs du bundle #}

{% for key, message in [Link]() %}

<div class="alert alert-{{ key }}">

{{ message|trans({}, 'FOSUserBundle') }}

</div>

{% endfor %}

{# On définit ce block, dans lequel vont venir s'insérer les autres vues du bundle #}

{% block fos_user_content %}

{% endblock fos_user_content %}

{% endblock %}
Pour créer ce layout je me suis simplement inspiré de celui fourni par FOSUserBundle,
en l'adaptant à notre cas.
Et voilà, si vous actualisez la page /login(après vous être déconnectés via /logout
évidemment), vous verrez que le formulaire de connexion est parfaitement intégré
dans notre design ! Vous pouvez également tester la page d'inscription sur /register,
qui est bien intégrée aussi.

Votre layout n'est pas pris en compte ? N'oubliez jamais d'exécuter la commande
php app/console cache : clear lorsque vous avez des erreurs qui vous étonnent !
Traduire les messages

FOSUB étant un bundle international, le texte est géré par le composant de traduction
de Symfony2. Par défaut, celui-ci est désactivé. Pour traduire le texte, il suffit donc de
l'activer (direction le fichier [Link]) et de décommenter une des premières lignes
dans framework :

# app/config/[Link]
framework:

translator: { fallback: %locale% }


Où %locale% est un paramètre défini dans app/config/[Link], et que vous
pouvez mettre à « fr » si ce n'est pas déjà fait. Ainsi, tous les messages utilisés par
FOSUserBundle seront traduits en français !

Afficher une barre utilisateur


Il est intéressant d'afficher dans le layout si le visiteur est connecté ou non, et d'afficher
des liens vers les pages de connexion ou de déconnexion. Cela se fait facilement, je
vous invite à insérer ceci dans votre layout, où vous voulez :

{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}

Connecté en tant que {{ [Link] }}

<a href="{{ path('fos_user_security_logout') }}">Déconnexion</a>

{% else %}

<a href="{{ path('fos_user_security_login') }}">Connexion</a>

{% endif %}
Adaptez et mettez ce code dans votre layout, effet garanti.

Le rôle IS_AUTHENTICATED_REMEMBERED est donné à un utilisateur qui s'est authentifié


soit automatiquement grâce au cookie remember_me, soit en utilisant le formulaire de
connexion. Le rôle IS_AUTHENTICATED_FULLY est donné à un utilisateur qui s'est
obligatoirement authentifié manuellement, en rentrant son mot de passe dans le
formulaire de connexion. C'est utile pour protéger les opérations sensibles comme le
changement de mot de passe ou d'adresse e-mail.
Manipuler les utilisateurs avec FOSUserBundle

Nous allons voir les moyens pour manipuler vos utilisateurs au quotidien.

Si les utilisateurs sont gérés par FOSUserBundle, ils ne restent que des entités
Doctrine2 des plus classiques. Ainsi, vous pourriez très bien vous créer un repository
comme vous savez le faire. Cependant, profitons du fait que le bundle intègre un
UserManager (c'est une sorte de repository avancé). Ainsi, voici les principales
manipulations que vous pouvez faire avec :

<?php

// Dans un contrôleur :
// Pour récupérer le service UserManager du bundle

$userManager = $this->get('fos_user.user_manager');

// Pour charger un utilisateur

$user = $userManager->findUserBy(array('username' => 'winzou'));

// Pour modifier un utilisateur

$user->setEmail('cetemail@[Link]');

$userManager->updateUser($user); // Pas besoin de faire un flush avec l'EntityManager, cette


méthode le fait toute seule !

// Pour supprimer un utilisateur

$userManager->deleteUser($user);

// Pour récupérer la liste de tous les utilisateurs

$users = $userManager->findUsers();
Si vous avez besoin de plus de fonctions, vous pouvez parfaitement faire un repository
personnel, et le récupérer comme d'habitude via $this->getDoctrine ()->
getManager () ->getRepository ('OCUserBundle : User'). Et si vous voulez en
savoir plus sur ce que fait le bundle dans les coulisses, n'hésitez pas à aller voir le
code des contrôleurs du bundle.

Pour conclure

Ce chapitre touche à sa fin. Vous avez maintenant tous les outils en main pour
construire votre espace membres, avec un système d'authentification performant et
sécurisé, et des accès limités pour vos pages suivant des droits précis.

Sachez que tout ceci n'est qu'une introduction à la sécurité sous Symfony2. Les
processus complets sont très puissants mais évidemment plus complexes. Si vous
souhaitez aller plus loin pour faire des opérations plus précises (authentification
Facebook, LDAP, etc.), n'hésitez pas à vous référer à la documentation officielle sur
la sécurité. Allez jeter un œil également à la documentation de FOSUserBundle, qui
explique comment personnaliser au maximum le bundle, ainsi que l'utilisation des
groupes.

Pour information, il existe également un système d'ACL, qui vous permet de définir
des droits bien plus finement que les rôles. Par exemple, pour autoriser l'édition d'une
annonce si on est admin ou si on en est l'auteur. Je ne traiterai pas ce point dans ce
cours, mais n'hésitez pas à vous référer à la documentation à ce sujet.

 La sécurité se compose de deux couches :


o L'authentification, qui définit qui est le visiteur ;
o L'autorisation, qui définit si le visiteur a accès à la ressource demandée.
 Le fichier [Link] permet de configurer finement chaque acteur de la
sécurité :
o La configuration de l'authentification passe surtout par le paramétrage
d'un ou plusieurs pare-feu ;
o La configuration de l'autorisation se fait au cas par cas suivant les
ressources : on peut sécuriser une méthode de contrôleur, un affichage
ou une URL.
 Les rôles associés aux utilisateurs définissent les droits dont ils disposent ;
 On peut configurer la sécurité pour utiliser FOSUserBundle, un bundle qui offre
un espace membres presque clé en main.

 #

VALIDEZ VOS DONNÉES

Vous aimerez peut-être aussi