带有新身份验证方法的 Symfony 简单登录表单不起作用

Posted

技术标签:

【中文标题】带有新身份验证方法的 Symfony 简单登录表单不起作用【英文标题】:Symfony simple Login form with the new authentication method not working 【发布时间】:2021-10-20 12:47:26 【问题描述】:

我在设置一个新的 symfony 应用程序时遇到问题,我确信它与新的基于 Authenticator 的安全系统有关。

    我安装了一个全新的 symfony 应用程序,版本 5.3.6。 安装了安全包composer require symfony/security-bundle(https://symfony.com/doc/current/security.html)。 按照那里的所有步骤操作。 之后我想构建一个简单的 遵循本指南的登录表单: https://symfony.com/doc/current/security/form_login_setup.html。一世 执行了命令php bin/console make:auth,它生成了所有 文件并像往常一样更新了我的security.yml。在这里我注意到 该命令没有生成 Guard 身份验证器(因为我 了解其已过时),但新 一个(https://symfony.com/doc/current/security/authenticator_manager.html)。 之后我进入我的 /login 页面,输入凭据并提交 表格。页面重新加载,什么也没有。没有错误信息,我是 仍未通过身份验证。我没有做任何额外的步骤,因为它 应该按原样工作吗?好吧,至少是旧的 Guard 身份验证 就这样工作。然而,这个新的身份验证系统似乎 不行。我错过了什么吗?

我的文件:

LoginFormAuthenticator.php

class LoginFormAuthenticator extends AbstractLoginFormAuthenticator

use TargetPathTrait;

public const LOGIN_ROUTE = 'app_login';

private UrlGeneratorInterface $urlGenerator;

public function __construct(UrlGeneratorInterface $urlGenerator)

    $this->urlGenerator = $urlGenerator;


public function authenticate(Request $request): PassportInterface

    $email = $request->request->get('email', '');

    $request->getSession()->set(Security::LAST_USERNAME, $email);

    return new Passport(
        new UserBadge($email),
        new PasswordCredentials($request->request->get('password', '')),
        [
            new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
        ]
    );


public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response

    if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) 
        return new RedirectResponse($targetPath);
    

    // For example:
    return new RedirectResponse($this->urlGenerator->generate('dashboard'));


protected function getLoginUrl(Request $request): string

    return $this->urlGenerator->generate(self::LOGIN_ROUTE);

SecurityController.php

class SecurityController extends AbstractController

/**
 * @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()

    throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');

security.yml

security:
# https://symfony.com/doc/current/security/experimental_authenticators.html
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#c-hashing-passwords
password_hashers:
    Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    App\Entity\User:
        algorithm: auto

# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
    # used to reload user from session & other features (e.g. switch_user)
    app_user_provider:
        entity:
            class: App\Entity\User
            property: email
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        lazy: true
        provider: app_user_provider
        custom_authenticator: App\Security\LoginFormAuthenticator
        logout:
            path: app_logout
            # where to redirect after logout
            # target: app_any_route

        # activate different ways to authenticate
        # https://symfony.com/doc/current/security.html#firewalls-authentication

        # https://symfony.com/doc/current/security/impersonating_user.html
        # switch_user: true

# 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: ^/admin, roles: ROLE_ADMIN 
    # -  path: ^/profile, roles: ROLE_USER 

【问题讨论】:

您发布的内容看起来不错。我假设您确实按照您发布的代码所示调整了 onAuthenticationSuccess。尝试注释掉管理员访问控制行,以确保它没有搞砸。 @Cerad 问题是,onAuthenticationSuccess 甚至没有被调用。更重要的是,甚至没有调用 authenticate() 方法。我在 authenticate 方法中添加了die;,提交了表单并重新加载了页面,这意味着我什至没有到达 authenticate() 方法...... 您似乎定义了 app_login ,这意味着 support() 应该可以工作。即使发生了某种 csrf 废话,它仍然应该到达身份验证方法。你说这是一个新的应用程序,所以不应该有任何其他听众拦截东西。您的成功路线被命名为仪表板,但我假设您没有加载任何管理类型的包?开发服务器的控制台窗口中是否有任何有用的消息? @Cerad 我检查了 supports() 方法并发现了问题......虽然也许你可以帮助我解决它。由于我将 wamp 用于开发目的,getLoginUrl() 方法返回我的完整路径:/workspace/public/login,但 getPathInfo() 只是 /login,因此 support() 方法总是返回 false... 无论如何我可以处理吗?编辑:我覆盖了supports() 方法并将getPathInfo() 更改为getRequestUri() 并修复了它......最后......我会写下答案。谢谢! 我认为您可能需要一个 htaccess 文件来摆脱 /workspace/public。可能会在其他地方搞砸你。更好的是,只需使用 Symfony 开发服务器。这就是它的用途。 【参考方案1】:

在 AbstractLoginFormAuthenticator 类的 supports() 方法中发现了问题。该方法检查 getLoginUrl() 方法是否返回完整的 URI,而 getPathInfo() 在 URI 之后给出额外的路径信息。我不得不用 getRequestUri() 覆盖supports() 方法和thange getPathInfo() 并且它起作用了。

public function supports(Request $request): bool

    return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getRequestUri();

【讨论】:

如果您使用反向代理路径 (public_url//prefix => internal_url) 并实施trusted_headers + x-forwarded-prefix,这似乎也会发生。

以上是关于带有新身份验证方法的 Symfony 简单登录表单不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Symfony2 自定义身份验证:用户登录但未通过身份验证

Symfony 4,登录,如何调试错误“无效凭据”?

带有内置登录视图和身份验证表单的 Django “记住我”

Symfony 5 登录表单操作返回无效的 CSRF 令牌

表单身份验证重定向 css/脚本包含到带有 HTTP 302 的登录页面

在 Php/symfony 中检查用户身份验证