
在 symfony 5.3 中,当用户认证失败时,系统会通过 authenticationutils::getlastauthenticationerror() 方法获取错误信息,并在登录页面显示。然而,直接在 abstractloginformauthenticator 的 onauthenticationfailure 方法中抛出 customusermessageauthenticationexception 并不能直接将其消息传递到视图中。这是因为 onauthenticationfailure 方法本身是用来处理在认证流程中捕获到的 authenticationexception 的,而不是产生最终显示给用户的错误消息的源头。
Symfony 的认证管理器 AuthenticatorManager 在执行认证器 (AuthenticatorInterface::authenticate()) 过程中,如果捕获到 AuthenticationException,便会调用 handleAuthenticationFailure() 方法,进而触发认证器的 onAuthenticationFailure() 方法。
// Symfony\Component\Security\Http\Authentication\AuthenticatorManager.php
try {
// 获取认证器返回的 Passport
$passport = $authenticator->authenticate($request);
// ...
} catch (AuthenticationException $e) {
// 认证失败!
$response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport);
// ...
}
private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?PassportInterface $passport): ?Response
{
// ...
// 如果 hide_user_not_found 为 true 且异常不是 CustomUserMessageAccountStatusException,
// 则会将 UsernameNotFoundException 或 AccountStatusException 替换为 BadCredentialsException。
if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UsernameNotFoundException || ($authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException))) {
$authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException);
}
$response = $authenticator->onAuthenticationFailure($request, $authenticationException);
// ...
}从上述代码可以看出,onAuthenticationFailure() 方法接收的是一个已经存在的 AuthenticationException 对象。默认情况下,AbstractLoginFormAuthenticator 的 onAuthenticationFailure 方法会将这个异常对象存储到会话中 ($request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);),而 AuthenticationUtils::getLastAuthenticationError() 正是从会话中检索此异常。因此,如果你想自定义错误消息,你需要在认证流程中更早的地方抛出带有你自定义消息的异常。
此外,需要特别注意 hide_user_not_found 配置项。如果此项为 true(默认值),Symfony 为了防止通过错误消息枚举用户,会将 UsernameNotFoundException 和某些 AccountStatusException 替换为通用的 BadCredentialsException('Bad credentials.')。这意味着,即使你抛出了 CustomUserMessageAuthenticationException,如果它属于这些被隐藏的类型,其自定义消息也可能被覆盖。
要成功显示自定义的认证错误消息,你需要:
为了确保 CustomUserMessageAuthenticationException 的消息不被覆盖,你可以在 config/packages/security.yaml 中将 hide_user_not_found 设置为 false:
# config/packages/security.yaml
security:
# ...
hide_user_not_found: false
firewalls:
# ...注意: 如果出于安全考虑不希望禁用 hide_user_not_found,则应考虑抛出 CustomUserMessageAccountStatusException 而不是 CustomUserMessageAuthenticationException,因为前者不会被 hide_user_not_found 机制替换。
你可以在以下几个关键点抛出 CustomUserMessageAuthenticationException:
在你的自定义认证器(继承自 AbstractLoginFormAuthenticator)的 authenticate() 方法中,你可以根据业务逻辑判断抛出自定义异常。例如,在验证凭据或加载用户时:
// src/Security/MyLoginFormAuthenticator.php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class MyLoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
// ... 其他方法
public function authenticate(Request $request): Passport
{
$email = $request->request->get('email', '');
$password = $request->request->get('password', '');
// 示例:如果邮箱格式不正确,可以抛出自定义异常
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new CustomUserMessageAuthenticationException('请输入有效的邮箱地址。');
}
// 实际的用户加载和密码验证逻辑
// UserBadge 会尝试通过 UserProvider 加载用户
return new Passport(
new UserBadge($email),
new PasswordCredentials($password)
);
}
// ... onAuthenticationSuccess, getLoginUrl 等方法
}如果用户无法通过其标识符(如邮箱或用户名)找到,你可以在用户提供者(通常是你的 UserRepository)的 loadUserByIdentifier() 方法中抛出异常:
// src/Repository/UserRepository.php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
/**
* @extends ServiceEntityRepository<User>
*
* @implements PasswordUpgraderInterface<User>
*/
class UserRepository extends ServiceEntityRepository implements UserProviderInterface, PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function loadUserByIdentifier(string $identifier): UserInterface
{
$user = $this->findOneBy(['email' => $identifier]);
if (!$user) {
// 用户不存在时抛出自定义消息
throw new CustomUserMessageAuthenticationException('该邮箱未注册,请检查或注册新账号。');
}
return $user;
}
// ... 其他方法
}用户检查器允许你在认证前 (checkPreAuth) 和认证后 (checkPostAuth) 对用户进行额外检查(例如,账户是否已禁用、是否已过期、是否需要验证邮箱等)。
// src/Security/UserChecker.php
namespace App\Security;
use App\Entity\User;
use Symfony\Component\Security\Core\Exception\AccountExpiredException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserChecker implements UserCheckerInterface
{
public function checkPreAuth(UserInterface $user): void
{
if (!$user instanceof User) {
return;
}
// 示例:如果用户未激活
if (!$user->isActivated()) {
throw new CustomUserMessageAuthenticationException('您的账户尚未激活,请检查您的邮箱进行激活。');
}
// 示例:如果账户已锁定
if ($user->isLocked()) {
throw new CustomUserMessageAuthenticationException('您的账户已被锁定,请联系管理员。');
}
}
public function checkPostAuth(UserInterface $user): void
{
if (!$user instanceof User) {
return;
}
// 示例:如果密码已过期
if ($user->isPasswordExpired()) {
throw new CustomUserMessageAuthenticationException('您的密码已过期,请重置密码。');
}
}
}确保你的 security.yaml 配置中包含了你的 UserChecker:
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
user_checker: App\Security\UserChecker
# ...在你的登录 Twig 模板中,通过 error.messageKey|trans 过滤器来显示错误消息。CustomUserMessageAuthenticationException 会自动将其构造函数中的消息作为 messageKey 传递。
{# templates/security/login.html.twig #}
{% block body %}
<form method="post">
{% if error %}
{# error.messageKey 将是 CustomUserMessageAuthenticationException 中传入的消息 #}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{# ... 其他登录表单字段 #}
</form>
{% endblock %}定制 Symfony 5.3 中的认证错误消息需要理解其内部流程。关键在于:
通过遵循这些步骤,你将能够灵活地为用户提供清晰、定制化的认证失败提示,从而提升用户体验。
以上就是在 Symfony 5.3 中定制用户认证失败提示的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号