La sécurité dans une application Symfony
Présentation et concepts de sécurité
1. Les challenges de la sécurité des applications web
Lors du développement d’un site web, au fur et à mesure de l’ajout de certaines fonctionnalités, comme un espace « membres » ou une zone d’administration, la question de la sécurité devient rapidement une préoccupation importante.
Symfony décompose en deux étapes le processus par lequel l’accès à une ressource est restreint à un nombre limité d’utilisateurs : l’authentification et l’autorisation.
La première étape, l’authentification, consiste à identifier un utilisateur. Une fois l’utilisateur identifié, l’application est chargée de vérifier si ce dernier est autorisé à accéder à la ressource, ou à effectuer l’action demandée.
Au niveau du protocole HTTP, ces deux concepts sont notamment illustrés par les statuts 401 et 403. Le statut 401 signifie qu’une authentification est nécessaire pour accéder à la ressource. Le statut 403, quant à lui, indique que le serveur a correctement identifié l’utilisateur, mais que ce dernier n’est pas autorisé à accéder à la ressource.
2. La sécurité dans Symfony...
Authentification
1. Pare-feu
Le rôle du pare-feu est de définir les zones de l’application sujettes au système de sécurité.
Lorsque la requête d’un utilisateur correspond aux règles définies par un pare-feu, la sécurité est activée. La requête doit donc passer au travers de ce pare-feu et satisfaire les contraintes d’autorisations qui y sont spécifiées.
Une requête est associée à un pare-feu en fonction de son chemin (path). Reprenons la section firewalls de notre configuration de base :
# config/packages/security.yaml
security:
# ...
firewalls:
mon_pare_feu:
pattern: ^/
Ici, nous définissons un pare-feu nommé mon_pare_feu. La directive pattern contient l’expression régulière à appliquer sur le chemin de la requête pour déterminer si elle doit passer par le pare-feu ou non. Comme tous les chemins commencent obligatoirement par un slash, l’expression régulière ci-dessus fait que toutes les requêtes passeront par le pare-feu mon_pare_feu.
Pare-feu pour un sous-domaine
En plus du path, le pare-feu peut analyser le nom de domaine de la requête. Cela se fait avec la directive host :
host:
pattern: ^/
host: ma-section\.example\.com
http_basic: ~
Ici, le nom de domaine ma-section.example.com requiert une authentication HTTP. Nous reviendrons sur cette technique plus loin dans ce chapitre.
Pare-feu pour ressources statiques/développement
Certains chemins doivent toujours être accessibles. C’est notamment le cas des ressources statiques (images, feuilles de style, etc.) ou des URL utilisées en interne par Symfony (comme la barre de débogage du Profiler).
Pour éviter de sécuriser malencontreusement ce type de ressources, nous vous conseillons de créer un pare-feu sans règle de sécurité pour ces dernières :
security:
# ...
firewalls:
dev:
pattern:...
Utilisateurs et rôles
1. L’utilisateur
Avec Symfony, l’objet représentant un utilisateur implémente obligatoirement l’interface Symfony\Component\Security\Core\User\UserInterface. Elle garantit les méthodes suivantes :
-
getUserIdentifier(), qui retourne le nom d’utilisateur.
-
getRoles(), qui retourne les rôles de l’utilisateur. Ces rôles ont un impact sur le niveau d’autorisation de chaque membre. En pratique, ils permettent, par exemple, de dire si le membre est un administrateur, un modérateur ou un simple utilisateur.
-
eraseCredentials(), qui est utilisée en interne par le framework. Cette méthode efface les informations sensibles (s’il en existe) contenues dans l’objet utilisateur. Elle est utile si, par exemple, à un moment donné, le mot de passe est stocké en clair dans l’objet.
Selon le fournisseur d’utilisateurs que vous choisissez ou créez, les objets représentant les utilisateurs peuvent implémenter une classe plus complète que UserInterface : Symfony\Component\Security\Core\User\AdvancedUserInterface. Le but de cette dernière est de prendre en charge les fonctionnalités de suspension et d’expiration de comptes utilisateur.
Si l’authentification des utilisateurs s’appuie sur un nom d’utilisateur associé à un mot de passe, alors l’objet représentant l’utilisateur doit également implémenter l’interface Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface. Elle garantit la méthode getPassword() qui doit retourner le mot de passe de l’utilisateur authentifié.
Récupérer l’utilisateur courant
Pour récupérer l’utilisateur actuellement authentifié, vous disposez du raccourci getUser() dans vos contrôleurs :
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
#[Route('/')]
public function index()
{
$utilisateur = $this->getUser(); ...
Autorisations
Jusqu’ici, nous avons appris à authentifier des utilisateurs, au travers de différentes méthodes et de différents fournisseurs d’utilisateurs. Nous connaissons la représentation d’un utilisateur et nous savons qu’il possède des rôles. Nous allons maintenant voir comment sécuriser des ressources.
1. Les rôles, au cœur du processus
Le principe de l’autorisation est simple : vos utilisateurs possèdent des rôles et l’accès à certaines ressources peut être limité à certains rôles. Les rôles sont donc l’élément central du processus d’autorisation. Avant de continuer, il est nécessaire de vous présenter de nouveaux rôles.
Si un utilisateur est derrière l’un des pare-feu que vous avez défini (dans config/packages/security.yaml), il possède obligatoirement au moins un des rôles suivants :
-
IS_AUTHENTICATED_ANONYMOUSLY : l’utilisateur n’est pas authentifié (il est derrière un pare-feu qui autorise les utilisateurs anonymes).
-
IS_AUTHENTICATED_REMEMBERED : l’utilisateur est authentifié grâce à la fonctionnalité « se souvenir de moi ».
-
IS_AUTHENTICATED_FULLY : l’utilisateur s’est authentifié dans la requête ou session courante.
Vous n’avez pas à gérer ces rôles dans la méthode getRoles() de votre objet utilisateur car ils sont générés automatiquement par Symfony.
2. Vérifier le rôle de l’utilisateur
Pour sécuriser l’une de vos actions, vous ne devez jamais vous baser sur les rôles retournés par l’objet utilisateur. Les rôles que nous venons de présenter (IS_AUTHENTICATED_*) ne sont par exemple pas présents au niveau de l’objet utilisateur car Symfony utilise en interne un jeton (ou token) pour vérifier une autorisation.
Voici comment vérifier qu’un rôle donné a été accordé à l’utilisateur courant :
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; ...