Revenez sur la sécurité des applications web
Une fois encore, Amélie vous envoie un e-mail.
Bravo pour tout ce travail ! Les formulaires fonctionnent, je peux ajouter tout le catalogue, et on peut consulter toutes les informations utiles pour le public. Par contre je m’inquiète un peu. J’ai l’impression que pour l’instant tout le monde peut accéder aux formulaires et changer le contenu des données ! Pourriez-vous y remédier ?
Ça tombe bien, c’était précisément ce que Sébastien voulait vous expliquer. Il est donc temps de se pencher sur la sécurité dans les applications Symfony. Mais avant ça, il convient de faire un petit rappel sur la manière dont fonctionne la sécurité dans les applications web de manière générale.
Lorsqu’une nouvelle requête arrive dans une application, plusieurs questions vont se poser :
En premier lieu, la requête vise-t-elle une route de notre application ? Autrement dit, est-ce que cette requête est bien pour nous ?
Si oui, cette route est-elle protégée ? Il n’y a bien sûr pas lieu de mettre en route la sécurité de l’application si la page est publique.
Si la page est protégée, on rentre dans la partie sécurisée de l’application, et d’autres questions arrivent :
Est-ce que je connais l’utilisateur qui fait la requête ? Et si je connais l’utilisateur, qu’est-ce qui me prouve que c’est lui ? Ici par exemple, on va essayer de savoir si dans notre base de données il y a un utilisateur avec cette adresse e-mail. Ensuite, nous allons vérifier les mots de passe. On appelle cette étape l'Authentification.
Et maintenant que je suis sûr de l’identité de l’utilisateur, est-ce qu’il a le droit d’afficher cette page ? Un utilisateur peut être connecté, s'il n’est pas administrateur du site, il n’a pas accès à l’administration, par exemple. Cette étape s’appelle l’Autorisation.
C’est toujours mieux avec un schéma, Sébastien vous en fournit donc un :
Vous retrouverez ce principe dans à peu près toutes les applications web du monde. Il est en tout cas implémenté de cette manière dans Symfony. Une chose est intéressante dans ce schéma cependant. En regardant bien, on voit que le sujet de toutes les questions de sécurité, c’est l’utilisateur. C’est donc l’objet central de la sécurité, et il va avoir besoin d’une représentation particulière dans notre application.
Représentez vos utilisateurs avec l’interface UserInterface
Notre utilisateur représente donc le sujet qui a déclenché une requête. Pour qu’il puisse être utilisé partout dans le système de sécurité de Symfony, il doit remplir une condition simple : implémenter l’interface Symfony\Component\Security\Core\User\UserInterface
.
Cette interface définit les méthodes obligatoires qui seront utilisées par notre système de sécurité, mais elle ne définit pas de moyen de gérer les mots de passe. Pour une raison très simple : les applications modernes ne se basent pas toutes sur des mots de passe pour gérer leur sécurité.
Comme ce n’est pas le cas de notre application, nous allons devoir prendre en compte les mots de passe et implémenter une seconde interface sur notre classe d’utilisateur : Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface
.
Détaillons-les rapidement.
Pour UserInterface
:
Nous avons
getUserIdentifier
qui permet de retourner la chaîne de caractères qui identifie notre utilisateur quand il se connecte, qui est aussi appelélogin
sur plein de sites internet. Ce peut être une chaîne arbitraire appelée iciusername
, ou son adresse e-mail, ou ce que vous voulez d’autre. Rappelez-vous juste que c’est ce que l’utilisateur doit entrer dans un formulaire de connexion, donc le plus simple est généralement le mieux.getRoles
permet de retourner un array de chaînes de caractères appelées roles, et qui permet d’aider à déterminer les droits de l’utilisateur. Nous reviendrons dessus au chapitre 3 de cette partie.eraseCredentials
est une fonction dont nous pourrons nous servir pour éviter de garder en mémoire le mot de passe de l’utilisateur avant hachage, mais ce n’est pas obligatoire.
Du côté de PasswordAuthenticatedUserInterface
c’est plus simple, nous avons uniquement getPassword
, qui permet de récupérer le mot de passe haché. Nous reviendrons sur cette notion au chapitre suivant.
Notez que comme nous utilisons déjà Doctrine, le plus simple pour nous sera de sauvegarder nos utilisateurs en base de données. Fort heureusement, rien ne nous empêche d’implémenter ces interfaces sur une entité, c’est même tout à fait classique. Et c’est donc ce que nous allons faire.
Créez une classe User
MakerBundle vous propose une commande
make:user
spécifiquement pour créer une classe qui implémenteUserInterface
.Grâce à cette commande, vous pouvez aussi choisir d'implémenter
PasswordAuthenticatedUserInterface
ou non.Vous pouvez aussi choisir de faire de votre classe une entité ou non.
La commande gère pour vous les changements à apporter à la configuration de sécurité dans
config/packages/security.yaml
.
Récupérez vos utilisateurs en base de données
Le fichier config/packages/security.yaml
contient déjà beaucoup d’informations, mais une en particulier nous intéresse tout de suite. Il s’agit des lignes situées sous la clé providers
:
:
# …
:
# used to reload user from session & other features (e.g. switch_user)
:
:
: App\Entity\User
: username
# …
Alors je vois bien que ça parle de notre nouvelle entité User
, mais c’est quoi un provider ?
C’est exactement la clé de l’authentification. Rappelez-vous la définition que nous en avons donnée au début du chapitre. La sécurité se passe en deux temps : l’authentification (“Est-ce que je connais l’utilisateur ?”), puis l’autorisation (“Cet utilisateur a-t-il le droit de faire ça ?”).
Eh bien en fait, la première étape, l’authentification, se passe en deux temps :
D’abord, je vais récupérer l’identifiant envoyé avec la requête et vérifier qu’un utilisateur possédant ce même identifiant existe dans mes sources de données.
S'il existe bien un utilisateur avec cet identifiant dans mes sources, je vais vérifier que son mot de passe correspond bien à celui envoyé avec la requête.
Ou plus concrètement, dans notre cas, je vais vérifier qu’une entité User
existe avec le username qui m’a été envoyé. Et si oui, je vérifierai son mot de passe.
Eh bien la première partie de cette vérification incombe à ce qu’on appelle des UserProviders. Leur travail est simple : on leur donne un identifiant, et ils regardent dans la source à laquelle ils sont rattachés.
Pour l’instant nous avons un seul provider, nommé app_user_provider
. La configuration nous dit ensuite que c’est un provider de type entity
. Enfin, il est spécifié que l’entité concernée est App\Entity\User
, et que la propriété qui sera utilisée pour chercher nos utilisateurs est username
.
À vous de jouer
Contexte
Nous avons créé notre entité User, mais elle est pour l'instant assez vide. De plus, à la réflexion, Sébastien vous fait remarquer qu'utiliser le username comme moyen de connexion n'est peut-être pas ce qu'il y a de plus naturel pour des utilisateurs pas forcément toujours expérimentés en informatique. Il va donc falloir modifier tout cela.
Consignes
Vous allez commencer par ajouter trois propriétés à votre entité User :
firstname
, une chaîne de caractères de 255 caractères de long, non nullable ;lastname
, avec les mêmes caractéristiques ;email
, toujours avec les mêmes caractéristiques, mais qui doit en plus être unique (vous devrez ajouter cette précision à la main dans votre fichier, MakerBundle ne peut pas l'automatiser).
N'oubliez pas :
de retirer l'index unique de la propriété
username
;de rendre nullable cette même propriété ;
d'ajouter des contraintes de validation ;
et bien entendu les migrations qui permettront ces changements en base de données.
Ensuite, vous indiquerez à Symfony qu'en fait, il faudra se baser sur l'e-mail pour récupérer vos utilisateurs.
En résumé
La sécurité dans Symfony fonctionne en deux étapes : Authentification et Autorisation.
Pour représenter l’utilisateur qui déclenche une requête, Symfony a besoin d’un objet qui implémente
UserInterface
.Cet objet utilisateur est chargé durant l’authentification au moyen d’un UserProvider.
Symfony embarque nativement plusieurs types de UserProviders, dont le type entité qui cherche les utilisateurs en base de données.
Et maintenant, voyons comment vérifier l’identité de nos utilisateurs afin de les authentifier !