Symfony新认证方法的简单登录表单无法工作。

5

我在设置新的Symfony应用程序时遇到了问题,我确定是与新的基于Authenticator的安全系统有关。

  1. 我安装了一个新的Symfony应用程序,版本为5.3.6。
  2. 安装了安全包composer require symfony/security-bundle (https://symfony.com/doc/current/security.html)。按照其中的所有步骤操作。
  3. 之后,我想根据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)。
  4. 之后,我进入我的/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
@Cerad,问题在于onAuthenticationSuccess甚至没有被调用。更糟糕的是,authenticate()方法甚至没有被调用。我在authenticate方法中添加了die;,提交表单后页面重新加载,这意味着我甚至没有到达authenticate()方法... - domskat
你似乎已经定义了app_login,这意味着supports()应该可以工作。即使有某种csrf的问题,它仍然应该到达authenticate方法。你说这是一个新的应用程序,所以不应该有其他拦截器拦截事情。你的成功路由被命名为dashboard,但我假设你没有加载任何管理员类型的包?在开发服务器的控制台窗口中有任何有用的消息吗? - Cerad
1
@Cerad 我检查了supports()方法并找到了问题...不过也许你可以帮我处理一下。由于我正在使用wamp进行开发,getLoginUrl()方法返回我的完整路径:/workspace/public/login,但getPathInfo()只是/login,所以supports()方法总是返回false...有什么办法可以解决吗?编辑:我重写了supports()方法,并将getPathInfo()更改为getRequestUri(),这样就解决了...终于...我会写答案的。谢谢! - domskat
我认为你可能需要一个htaccess文件来摆脱/workspace/public。这样做可能会在其他地方给你带来麻烦。最好的方法是使用Symfony开发服务器,因为它就是为此而设计的。 - Cerad
1个回答

8
问题出现在AbstractLoginFormAuthenticator类的supports()方法中。该方法检查getLoginUrl()方法是否返回完整的URI,而getPathInfo()方法则给出URI后的额外路径信息。我不得不重写supports()方法并将getPathInfo()更改为getRequestUri(),这样它就能正常工作了。
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,似乎也会发生这种情况。 - Teenage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接