【问题标题】:Customize new authentication errors messages on Symfony5.3在 Symfony5.3 上自定义新的身份验证错误消息
【发布时间】:2021-12-09 07:42:39
【问题描述】:

在我的新 symfony 5.3 项目中,我刚刚实现了新的身份验证系统,它工作正常,但我的问题是我无法自定义身份验证错误:

方法中:AbstractLoginFormAuthenticator中的onAuthentificationFailure 但在我看来,它只显示会话错误,这是正常的,因为我的控制器调用了 getLastAuthenticationError() 方法。

但是我如何显示我的自定义错误 CustomUserMessageAuthenticationException 在我看来?

我的 AbstractLoginFormAuthenticator




namespace Symfony\Component\Security\Http\Authenticator;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;

/**
 * A base class to make form login authentication easier!
 *
 * @author Ryan Weaver <ryan@symfonycasts.com>
 */
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface
{
    /**
     * Return the URL to the login page.
     */
    abstract protected function getLoginUrl(Request $request): string;

    /**
     * {@inheritdoc}
     *
     * Override to change the request conditions that have to be
     * matched in order to handle the login form submit.
     *
     * This default implementation handles all POST requests to the
     * login path (@see getLoginUrl()).
     */
    public function supports(Request $request): bool
    {
        return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getPathInfo();
    }

    /**
     * Override to change what happens after a bad username/password is submitted.
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
    {
        if ($request->hasSession()) {
           //$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); 
           throw new CustomUserMessageAuthenticationException('error custom ');

        }
        

        $url = $this->getLoginUrl($request);

        return new RedirectResponse($url);
    }

    /**
     * Override to control what happens when the user hits a secure page
     * but isn't logged in yet.
     */
    public function start(Request $request, AuthenticationException $authException = null): Response
    {
        $url = $this->getLoginUrl($request);

        return new RedirectResponse($url);
    }

    public function isInteractive(): bool
    {
        return true;
    }
}

我的安全控制器:




namespace App\Controller;

use App\Entity\User;
use DateTimeImmutable;
use App\Form\RegistrationType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;


class SecurityController extends AbstractController {

    /**
     * @Route("/test", name="test")
     */
    public function test(Request $request, Response $response){
       
    
      

      return $this->render('test.html.twig');
      dump($response);
      

    }

    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // if ($this->getUser()) {
        //     return $this->redirectToRoute('target_path');
        // }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    /**
     * @Route("/logout", name="app_logout")
     */
    public function logout(): void
    {
        throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
    }

    /**
     * @Route("/Inscription", name="registration")
     */
    public function registration(EntityManagerInterface $manager, Request $request, UserPasswordHasherInterface $passwordEncoder): Response
    {
        $user = new User;
        $registrationForm = $this->createForm(RegistrationType::class, $user);
        $registrationForm->handleRequest($request);
        if($registrationForm->isSubmitted() && $registrationForm->isValid()){
           
           $plainPassword = $registrationForm->getData()->getPassword();
           $user->setPassword($passwordEncoder->hashPassword($user, $plainPassword));
           $user->setCreatedAt( new \DateTimeImmutable('NOW'));
           $user->setRoles($user->getRoles());
           $manager->persist($user);
           $manager->flush();
        }
       
        return $this->render('security/registration.html.twig',
        ['registrationForm' => $registrationForm->createView()]);
    }



}



我的观点(登录信息):


{% block title %}Log in!{% endblock %}

{% block body %}
<form method="post">
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    {% if app.user %}
        <div class="mb-3">
            You are logged in as {{ app.user.username }}, <a href="{{ path('app_logout') }}">Logout</a>
        </div>
    {% endif %}

    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputEmail">Email</label>
    <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" autocomplete="email" required autofocus>
    <label for="inputPassword">Password</label>
    <input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required>

    <input type="hidden" name="_csrf_token"
           value="{{ csrf_token('authenticate') }}"
    >

    {#
        Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
        See https://symfony.com/doc/current/security/remember_me.html

        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" name="_remember_me"> Remember me
            </label>
        </div>
    #}

    <button class="btn btn-lg btn-primary" type="submit">
        Sign in
    </button>
</form>
{% endblock %}

【问题讨论】:

    标签: php symfony authentication symfony5


    【解决方案1】:

    您应该扩展和覆盖AbstractLoginFormAuthenticator,而不是直接修改它。

    为什么你的方法不起作用

    简而言之,您需要在到达 onAuthenticationFailure() 之前抛出异常,因为 AuthenticationException 是 Symfony 调用 onAuthenticationFailure() 的原因。

    onAuthenticationFailure() 方法是处理从AuthenticatorManager::executeAuthenticator() 进程抛出的AuthenticationException

            try {
                // get the passport from the Authenticator
                $passport = $authenticator->authenticate($request);
    
                //...
    
            } catch (AuthenticationException $e) {
                // oh no! Authentication failed!
                $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport);
    
                // ...
            }
    
    //...
    
        private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?PassportInterface $passport): ?Response
        {
            // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
            // to prevent user enumeration via response content comparison
            if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) {
                $authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
            }
    
            $response = $authenticator->onAuthenticationFailure($request, $authenticationException);
    
            //...
        }
    

    AbstractLoginFormAuthenticator::onAuthenticationFailure() 的默认功能使用 $request-&gt;getSession()-&gt;set(Security::AUTHENTICATION_ERROR, $exception);,这是从异常中添加消息的内容,通过调用 AuthenticationUtils::getLastAuthenticationError(); 检索该消息,该消息调用 $session-&gt;get(Security::AUTHENTICATION_ERROR);

        public function getLastAuthenticationError(bool $clearSession = true)
        {
            $request = $this->getRequest();
            $authenticationException = null;
    
            //...
    
            if ($request->hasSession() && ($session = $request->getSession())->has(Security::AUTHENTICATION_ERROR)) {
                $authenticationException = $session->get(Security::AUTHENTICATION_ERROR);
                
                //...
            }
    
            return $authenticationException;
        }
    

    如何显示自定义错误消息

    应用程序中有多个被调用点,您可以在其中抛出传递给onAuthenticationFailure() 的异常。

    默认情况下,Symfony 使用安全配置设置来生成通用消息,但可以通过在您的类中实现它们来覆盖它们。

    由于异常被上述条件覆盖。

    if ($this->hideUserNotFoundExceptions && !$e instanceof CustomUserMessageAccountStatusException) {
        throw new BadCredentialsException('Bad credentials.', 0, $e);
    }
    

    您需要禁用hide_user_not_found,否则CustomUserMessageAuthenticationException 将被替换为BadCredentialsException('Bad credentials.') 异常。

    否则跳过禁用hide_user_not_found 并抛出CustomUserMessageAccountStatusException 而不是CustomUserMessageAuthenticationException

    # /config/packages/security.yaml
    
    security:
    
        # ...
        
        hide_user_not_found: false
    
        firewalls:
            # ...
    

    Authenticator class

    有关最新的使用参考,请阅读默认的FormLoginAuthenticator。由于 Symfony Casts 网站上的教程尚未针对 Symfony 5 进行更新。

    use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
    
    class MyLoginFormAuthenticator extends AbstractLoginFormAuthenticator
    {
    
        //...
    
        public function getCredentials(Request $request)
        {
           //...
           throw new CustomUserMessageAuthenticationException('Custom Error');
        }
    
        public function authenticate(Request $request): PassportInterface
        {
            //general usage of how the other exceptions are thrown here
            $credentials = $this->getCredentials($request);
    
            $method = 'loadUserByIdentifier';
            $badge = new UserBadge($credentials['username'], [$this->userProvider, $method]),
            $user = $badge->getUser(); //UserRepository::loadUserByIdentifier()
    
            //...
            throw new CustomUserMessageAuthenticationException('Custom Error');
        }
    }
    

    UserProvider class

    class UserRepository extends ServiceEntityRepository implements UserProviderInterface
    {
    
       //....
    
        public function loadUserByIdentifier(string $identifier)
        {
            throw new CustomUserMessageAuthenticationException('Custom Error');
    
            //...
        }
    
        public function loadUserByUsername(string $username)
        {
            return $this->loadUserByIdentifier($username);
        }
    }
    

    UserChecker class

    use Symfony\Component\Security\Core\User\{UserCheckerInterface, UserInterface};
    
    class UserChecker implements UserCheckerInterface
    {
        public function checkPreAuth(UserInterface $user): void
        {
            throw new CustomUserMessageAuthenticationException('Custom Error');
    
            //...
        }
    
        public function checkPostAuth(UserInterface $user): void
        {
            throw new CustomUserMessageAuthenticationException('Custom Error');
    
            //...
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-09-09
      • 1970-01-01
      • 2016-01-20
      • 2012-03-01
      • 2019-09-22
      • 1970-01-01
      • 1970-01-01
      • 2016-05-28
      相关资源
      最近更新 更多