启用Symfony 6.3同时通过用户名和电子邮件登录

xmjla07d  于 2023-10-24  发布在  其他
关注(0)|答案(2)|浏览(170)

问题:通过用户名(和密码)登录或通过电子邮件(和密码)登录都很容易,但如何启用这两种方式以获得最佳用户体验?
方法:我在Stackoverflow* 和各种教程上找到了多个答案,没有一个适用于我的Symfony版本(6.3.3),或者我只是太笨了,让他们工作(专业的Web开发人员)。

  • 创建我自己的AbstractAuthenticator扩展。失败的原因是:a)需要复制、修改大量逻辑。b)过多地使用复杂的登录逻辑功能会带来安全风险。c)我未能让一个重要的构造函数服务在我的扩展中工作。
  • 或者,我可以扩展一些内部Symfony类,但它们是final类,因此扩展它们不是一个有效的选择。
  • 手动登录是可能的,但这只是将上述问题转移到其他地方(构造“密码”、“徽章”、“令牌”等)。

解决方案:实际上我找到了一个很好的解决方法,我仍然问这个问题的原因是a)与其他人分享我的解决方案,B)也许它有什么问题,我没有看到它,还有更好的方法吗?

azpvetkf

azpvetkf1#

如果您使用的是symfony maker auth,只需在se src/Security/中编辑authenticate方法

class CustomAuthenticator extends AbstractLoginFormAuthenticator
    {
        use TargetPathTrait;
        public function __construct(private UserRepository, $userRepository){}
        public const LOGIN_ROUTE = 'app_login';
    
        public function authenticate(Request $request): Passport
        {
            $user_identificator = $request->request->get('user_identificator', '');
            $password = $request->request->get('password', '');
            // check if user_identifactor contains @ eg email
            if (str_contains($user_identificator, '@')) {
                // login with email@
                $user = $this-userRepository->findOneBy(['email'=> $user_identificator]);
            } else{
                // login with usename
                 $user = $this-userRepository->findOneBy(['uname'=> $user_identificator]);
            }
            if($user == null){
                // handle no user found ?
            }
            $request->getSession()->set(Security::LAST_USERNAME, $user_identificator);
            return new Passport(
                new UserBadge($user->getEmail()),
                new PasswordCredentials($request->request->get('password', '')),
                [
                    new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),            ]
            );
        }

然后需要在templates/security/login.html. wig中更改登录表单的模板
将电子邮件的默认输入更改为

<input type="text" value="{{ last_username }}" name="user_identificator" id="user_identificator" class="form-control" required autofocus>
odopli94

odopli942#

所以,我的解决方案是:
而不是执行以下操作:
1.登录表单
1.重定向到登录路由,其中Symfony登录魔术发生
1.重定向到登录控制器,在那里我可以编辑结果(如果成功,重定向,如果不成功,错误消息)
我在步骤1和步骤2之间添加了另一个操作:
1.登录表单:

<form action="{{ path('pre_security_login') }}" method="post" class="d-flex">
    <input type="text" name="email_or_username" required="required" placeholder="email or username">
    <input type="password" name="_password" required="required" placeholder="password">
    <input type="submit" name="login" value="Login">
    <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
    <input type="hidden" name="_target_path" value="{{ app.request.get('redirect_to') }}">
</form>

1.5重定向到我自己的控制器操作,不触及Symfony登录逻辑:Controller/AuthenticationController.php

...
class AuthenticationController extends BaseController
...
    #[Route("/pre_login", name: "pre_security_login")]
    public function PreAuthenticationAction(Request $request, UserRepository $userRepository): Response
    {
        if ($this->getUser()) {
            return $this->redirectToRoute(HomeController::ROUTE_HOME);
        }

        $usernameOrEmail = $request->request->get('email_or_username');
        if (str_contains($usernameOrEmail, '@')) {
            $email = $usernameOrEmail;
            $username = $userRepository->findOneBy(['email ' => $email ])?->getUsername() ?? '';
        } else {
            $username = $usernameOrEmail;
        }

        $password = $request->request->get('_password');
        $token = $request->request->get('_csrf_token');

        return $this->redirectToRoute('security_login', [
            '_username' => $username,
            '_password' => $password,
            '_csrf_token' => $token,
        ]);
    }

1.现在,真实的登录发生了,使用Symfony逻辑。

'firewalls' => [
...
            'main' => [
...
                'form_login' => [
                    'check_path' => 'security_login',
                    'login_path' => 'access_denied',
                    'form_only' => false, // <-- important, as it's no longer a form
                    'post_only' => false, // <-- important, as it's now a get request
                    'enable_csrf' => true,
                    'default_target_path' => 'home',
                ],
            ],
        ],
...

第三步与以前一样:

class AuthenticationController extends BaseController
...
    #[Route("/login", name: "security_login")]
    public function loginAction(AuthenticationUtils $authenticationUtils): Response
    {
        if ($this->getUser()) {
            return $this->redirectToRoute('home');
        }

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

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

相关问题