Symfony 4 Multiple guard authenticator

Par CedLP, il y a 6 ans


Bonjour à tous, j'ai un petit soucis j'aimerai crée deux LoginFormAuthenticator l'un pour les utilisateur front et l'autre pour les utilisateur en Back.
J'ai regarder la doc officiel de symfony mais cependant rien ne marche pour moi, quand je tape /profile je suis toujours rediriger vers le formulaire de login pour l'administrateur.

Voici mon code
Security.yml

security: encoders: App\Entity\Users: algorithm: 'argon2i' # maximum memory (in KiB) that may be used to compute the Argon2 hash memory_cost: 1024 # number of times the Argon2 hash algorithm will be run time_cost: 2 # number of threads to use for computing the Argon2 hash threads: 2 # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: app_user_provider: entity: class: App\Entity\Users property: username firewalls: main: anonymous: true guard: authenticators: - App\Security\LoginFormAuthenticator logout: path: app_logout remember_me: secret: '%kernel.secret%' lifetime: 604800 path: / front: anonymous: true guard: authenticators: - App\Security\FrontLoginFormAuthenticator logout: path: app_logout remember_me: secret: '%kernel.secret%' lifetime: 604800 path: / # activate different ways to authenticate # http_basic: true # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate # form_login: true # https://symfony.com/doc/current/security/form_login_setup.html role_hierarchy: ROLE_ADMIN: ROLE_ADMIN ROLE_USER: [ROLE_USER] # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/profile, roles: ROLE_USER }

LoginFormAuthenticator pour le BACK

<?php namespace App\Security; use App\Entity\Users; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; use Symfony\Component\Security\Http\Util\TargetPathTrait; /** * Class LoginFormAuthenticator * * @package App\Security * */ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator { use TargetPathTrait; private $entityManager; private $router; private $csrfTokenManager; private $passwordEncoder; private $authorizationChecker; /** * LoginFormAuthenticator constructor. * * @param EntityManagerInterface $entityManager * @param RouterInterface $router * @param CsrfTokenManagerInterface $csrfTokenManager * @param UserPasswordEncoderInterface $passwordEncoder * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct( EntityManagerInterface $entityManager, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, AuthorizationCheckerInterface $authorizationChecker ) { $this->entityManager = $entityManager; $this->router = $router; $this->csrfTokenManager = $csrfTokenManager; $this->passwordEncoder = $passwordEncoder; $this->authorizationChecker = $authorizationChecker; } /** * {@inheritdoc} */ public function supports(Request $request) { return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST'); } /** * {@inheritdoc} */ public function getCredentials(Request $request) { $credentials = [ 'email' => strtolower($request->request->get('email')), 'password' => $request->request->get('password'), 'csrf_token' => $request->request->get('_csrf_token'), ]; $request->getSession()->set( Security::LAST_USERNAME, $credentials['email'] ); return $credentials; } /** * {@inheritdoc} */ public function getUser($credentials, UserProviderInterface $userProvider) { $token = new CsrfToken('authenticate', $credentials['csrf_token']); if (!$this->csrfTokenManager->isTokenValid($token)) { throw new InvalidCsrfTokenException(); } $user = $this->entityManager->getRepository(Users::class)->findOneBy(['email' => $credentials['email']]); if (!$user) { // fail authentication with a custom error throw new CustomUserMessageAuthenticationException('error.invalid_user_account'); } // user disabled if (!$user->isEnabled()) { throw new CustomUserMessageAuthenticationException('error.account_disabled'); } return $user; } /** * {@inheritdoc} */ public function checkCredentials($credentials, UserInterface $user) { return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); } /** * {@inheritdoc} */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { return new RedirectResponse($targetPath); } $urlName = $this->authorizationChecker->isGranted(Users::ROLE_ADMIN) ? 'admin_index' : 'index_front'; // : ($this->authorizationChecker->isGranted(Users::ROLE_CHEF_RESEAUX) ? 'app_login' : ''); return new RedirectResponse($this->router->generate($urlName)); } /** * {@inheritdoc} */ protected function getLoginUrl() { return $this->router->generate('app_login'); } }

LoginFormAuthenticator pour le Front

<?php namespace App\Security; use App\Entity\Users; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; use Symfony\Component\Security\Http\Util\TargetPathTrait; /** * Class LoginFormAuthenticator * * @package App\Security * */ class FrontLoginFormAuthenticator extends AbstractFormLoginAuthenticator { use TargetPathTrait; private $entityManager; private $router; private $csrfTokenManager; private $passwordEncoder; private $authorizationChecker; /** * LoginFormAuthenticator constructor. * * @param EntityManagerInterface $entityManager * @param RouterInterface $router * @param CsrfTokenManagerInterface $csrfTokenManager * @param UserPasswordEncoderInterface $passwordEncoder * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct( EntityManagerInterface $entityManager, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, AuthorizationCheckerInterface $authorizationChecker ) { $this->entityManager = $entityManager; $this->router = $router; $this->csrfTokenManager = $csrfTokenManager; $this->passwordEncoder = $passwordEncoder; $this->authorizationChecker = $authorizationChecker; } /** * {@inheritdoc} */ public function supports(Request $request) { return 'front_login' === $request->attributes->get('_route') && $request->isMethod('POST'); } /** * {@inheritdoc} */ public function getCredentials(Request $request) { $credentials = [ 'email' => strtolower($request->request->get('email')), 'password' => $request->request->get('password'), 'csrf_tokens' => $request->request->get('_csrf_tokens'), ]; $request->getSession()->set( Security::LAST_USERNAME, $credentials['email'] ); return $credentials; } /** * {@inheritdoc} */ public function getUser($credentials, UserProviderInterface $userProvider) { $token = new CsrfToken('authenticate', $credentials['csrf_tokens']); if (!$this->csrfTokenManager->isTokenValid($token)) { throw new InvalidCsrfTokenException(); } $user = $this->entityManager->getRepository(Users::class)->findOneBy(['email' => $credentials['email']]); if (!$user) { // fail authentication with a custom error throw new CustomUserMessageAuthenticationException('error.invalid_user_account'); } // user disabled if (!$user->isEnabled()) { throw new CustomUserMessageAuthenticationException('error.account_disabled'); } return $user; } /** * {@inheritdoc} */ public function checkCredentials($credentials, UserInterface $user) { return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); } /** * {@inheritdoc} */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { return new RedirectResponse($targetPath); } $urlName = $this->authorizationChecker->isGranted(Users::ROLE_USER) ? 'front_index' : 'index_front'; return new RedirectResponse($this->router->generate($urlName)); } /** * {@inheritdoc} */ protected function getLoginUrl() { return $this->router->generate('front_login'); } }

Merci d'avance pour votre aide

3 réponses

Digivia, il y a 6 ans

Hello,

Quel est l'intérêt de créer deux guards?
Il ne faut pas mélanger le process d'authentification avec celui d'autorisation. Tu as un seul provider (ton entité User), donc tous tes users sont dans la même base si je ne m'abuse? Donc l'authentification s'effectue de la même manière non?
Ce sont les autorisations qui changent, suivant le rôle dont ton utilisateur dispose...
Tu pourrais donc avoir un seul guard, qui permet l'authentification, et ensuite tu te bases sur le rôle pour gérer les autorisations (ce que tu fais déja très bien dans la section access_control)....
Un autre guard serait utile, si par exemple, tu avais un second provider (oAuth, LDAP...) et donc un process d'authentification différent.

CedLP, il y a 6 ans

Au fait je penser à deux guard pour le fait que l'autre est à l'user back et l'autre à l'user front, puis comme tu le dis un seul suffit et j'ai enlever l'autre, maintenant c'est la vue qui pose problème car si je tape /admin je suis toujours rediriger vera /login au lieu de login-front
j'aimerai juste que les user front ai leur vue pour le login et la même chose pour les user back

Digivia, il y a 6 ans

Pourquoi 2 vues différentes pour la même chose (authentification)? Ce qui est intéressant c'est de rediriger ensuite par défaut sur une page différente (ou sur la page précédement demandée)...