Connexion des utilisateurs (Authentification)
Presque aucune application web ne peut se passer d'un mécanisme de connexion des utilisateurs et de vérification des autorisations des utilisateurs. Dans ce chapitre, nous parlerons de :
- connexion et déconnexion des utilisateurs
- authentificateurs personnalisés
Dans les exemples, nous utiliserons l'objet de la classe Nette\Security\User, qui représente l'utilisateur actuel
et auquel vous pouvez accéder en le faisant passer via l'injection de
dépendances. Dans les presenters, il suffit d'appeler $user = $this->getUser()
.
Authentification
L'authentification désigne la connexion des utilisateurs, c'est-à-dire le processus par lequel on vérifie si
l'utilisateur est bien celui qu'il prétend être. Habituellement, il se prouve par un nom d'utilisateur et un mot de passe. La
vérification est effectuée par un soi-disant authentificateur. Si la connexion échoue, une
Nette\Security\AuthenticationException
est levée.
De cette manière, vous déconnectez l'utilisateur :
Et pour savoir s'il est connecté :
Très simple, n'est-ce pas ? Et Nette s'occupe de tous les aspects de sécurité pour vous.
Dans les presenters, vous pouvez vérifier la connexion dans la méthode startup()
et rediriger l'utilisateur non
connecté vers la page de connexion.
Expiration
La connexion de l'utilisateur expire en même temps que l'expiration du
stockage, qui est généralement la session (voir les paramètres d'expiration de
session). Cependant, il est possible de définir un intervalle de temps plus court, après lequel l'utilisateur sera
déconnecté. Pour cela, on utilise la méthode setExpiration()
, qui est appelée avant login()
. Comme
paramètre, indiquez une chaîne avec un temps relatif :
Si l'utilisateur a été déconnecté en raison de l'expiration de l'intervalle de temps, la méthode
$user->getLogoutReason()
l'indiquera, renvoyant soit la constante
Nette\Security\UserStorage::LogoutInactivity
(le délai a expiré) soit UserStorage::LogoutManual
(déconnecté par la méthode logout()
).
Authentificateur
Il s'agit d'un objet qui vérifie les informations d'identification, c'est-à-dire généralement le nom et le mot de passe. Une forme triviale est la classe Nette\Security\SimpleAuthenticator, que nous pouvons définir dans la configuration :
Cette solution convient plutôt à des fins de test. Nous allons montrer comment créer un authentificateur qui vérifiera les informations d'identification par rapport à une table de base de données.
L'authentificateur est un objet implémentant l'interface Nette\Security\Authenticator avec la méthode
authenticate()
. Sa tâche est soit de retourner une soi-disant identité, soit de lever
une exception Nette\Security\AuthenticationException
. Il serait possible d'y ajouter un code d'erreur pour distinguer
plus finement la situation : Authenticator::IdentityNotFound
et Authenticator::InvalidCredential
.
La classe MyAuthenticator
communique avec la base de données par le biais de Nette Database Explorer et travaille avec la table users
, où la
colonne username
contient le nom de connexion de l'utilisateur et la colonne password
contient l'empreinte du mot de passe. Après vérification du nom et du mot de passe, elle renvoie l'identité,
qui contient l'ID de l'utilisateur, son rôle (colonne role
dans la table), dont nous parlerons plus en détail plus tard, et un tableau avec d'autres données (dans notre cas, le nom d'utilisateur).
Nous ajoutons encore l'authentificateur à la configuration en tant que service du conteneur DI :
Événements $onLoggedIn, $onLoggedOut
L'objet Nette\Security\User
a des événements $onLoggedIn
et
$onLoggedOut
, vous pouvez donc ajouter des rappels qui seront appelés après une connexion réussie ou après la
déconnexion de l'utilisateur.
Identité
L'identité représente un ensemble d'informations sur l'utilisateur, renvoyé par l'authentificateur et ensuite stocké dans
la session, que nous obtenons à l'aide de $user->getIdentity()
. Nous pouvons ainsi obtenir l'id, les rôles et
d'autres données utilisateur, telles que nous les avons transmises dans l'authentificateur :
Ce qui est important, c'est qu'après la déconnexion via $user->logout()
, l'identité n'est pas
supprimée et reste disponible. Ainsi, même si l'utilisateur a une identité, il n'est pas nécessairement connecté. Si nous
voulions supprimer explicitement l'identité, nous déconnecterions l'utilisateur en appelant logout(true)
.
Grâce à cela, vous pouvez continuer à supposer quel utilisateur est devant l'ordinateur et, par exemple, lui afficher des offres personnalisées dans une boutique en ligne, mais vous ne pouvez afficher ses données personnelles qu'après connexion.
L'identité est un objet implémentant l'interface Nette\Security\IIdentity, l'implémentation par défaut est Nette\Security\SimpleIdentity. Et comme mentionné, elle est maintenue dans la session, donc si, par exemple, nous changeons le rôle de l'un des utilisateurs connectés, les anciennes données resteront dans son identité jusqu'à sa prochaine connexion.
Stockage de l'utilisateur connecté
Deux informations de base sur l'utilisateur, à savoir s'il est connecté et son identité, sont
généralement transmises dans la session. Ce qui peut être modifié. Le stockage de ces informations est géré par un objet
implémentant l'interface Nette\Security\UserStorage
. Deux implémentations standard sont disponibles, la première
transmet les données dans la session et la seconde dans un cookie. Il s'agit des classes
Nette\Bridges\SecurityHttp\SessionStorage
et CookieStorage
. Vous pouvez choisir le stockage et le
configurer très facilement dans la configuration security › authentication.
De plus, vous pouvez influencer la manière exacte dont le stockage de l'identité (sleep) et la restauration
(wakeup) se dérouleront. Il suffit que l'authentificateur implémente l'interface
Nette\Security\IdentityHandler
. Celle-ci a deux méthodes : sleepIdentity()
est appelée avant
l'écriture de l'identité dans le stockage et wakeupIdentity()
après sa lecture. Les méthodes peuvent modifier le
contenu de l'identité, ou la remplacer par un nouvel objet qu'elles retournent. La méthode wakeupIdentity()
peut
même retourner null
, ce qui déconnecte l'utilisateur.
À titre d'exemple, montrons la solution à la question fréquente de savoir comment mettre à jour les rôles dans l'identité
immédiatement après le chargement depuis la session. Dans la méthode wakeupIdentity()
, nous transmettons à
l'identité les rôles actuels, par exemple depuis la base de données :
Et maintenant, revenons au stockage basé sur les cookies. Il vous permet de créer un site web où les utilisateurs peuvent se
connecter sans avoir besoin de sessions. C'est-à-dire qu'il n'a pas besoin d'écrire sur le disque. D'ailleurs, le site web que
vous consultez actuellement, y compris le forum, fonctionne de cette manière. Dans ce cas, l'implémentation de
IdentityHandler
est une nécessité. En effet, nous ne stockerons dans le cookie qu'un jeton aléatoire représentant
l'utilisateur connecté.
Tout d'abord, dans la configuration, nous définissons le stockage souhaité à l'aide de
security › authentication › storage: cookie
.
Dans la base de données, nous créons une colonne authtoken
, dans laquelle chaque utilisateur aura une chaîne complètement aléatoire, unique et impossible à deviner d'une longueur suffisante
(au moins 13 caractères). Le stockage CookieStorage
ne transmet dans le cookie que la valeur
$identity->getId()
, donc dans sleepIdentity()
, nous remplaçons l'identité originale par une
identité substitutive avec authtoken
dans l'ID, et inversement, dans la méthode wakeupIdentity()
, nous
lisons l'identité complète depuis la base de données en fonction de l'authtoken :
Plusieurs connexions indépendantes
Il est possible d'avoir plusieurs utilisateurs connectés indépendamment au sein d'un même site web et d'une même session. Si, par exemple, nous voulons avoir une authentification distincte pour l'administration et la partie publique sur le site web, il suffit de définir un nom propre pour chacune d'elles :
Il est important de ne pas oublier de toujours définir l'espace de noms à tous les endroits appartenant à la partie concernée. Si nous utilisons des presenters, nous définissons l'espace de noms dans l'ancêtre commun pour la partie donnée – généralement BasePresenter. Nous le faisons en étendant la méthode checkRequirements() :
Plusieurs authentificateurs
La division de l'application en parties avec connexion indépendante nécessite généralement également différents
authentificateurs. Cependant, si nous enregistrions deux classes implémentant Authenticator dans la configuration des services,
Nette ne saurait pas laquelle attribuer automatiquement à l'objet Nette\Security\User
, et afficherait une erreur.
Par conséquent, nous devons limiter l'autowiring pour les authentificateurs
afin qu'il ne fonctionne que si quelqu'un demande une classe spécifique, par exemple FrontAuthenticator, ce que nous réalisons
en choisissant autowired: self
:
Nous définissons l'authentificateur de l'objet User avant d'appeler la méthode login(), donc généralement dans le code du formulaire qui le connecte :