56

我想在注册过程之后立即登录用户,而不通过登录表单。

这可能吗 ?我找到了一个解决方案FOSUserBundle,但我没有在我实际从事的项目中使用它。

这是我的security.yml,我正在使用两个防火墙。纯文本编码器仅用于测试。

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
        Ray\CentralBundle\Entity\Client: md5

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        in_memory:
            users:
                admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
        entity:
            entity: { class: Ray\CentralBundle\Entity\Client, property: email }

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        user_login:
            pattern:    ^/user/login$
            anonymous:  ~

        admin_login:
            pattern:    ^/admin/login$
            anonymous:  ~

        admin:
            pattern:    ^/admin
            provider: in_memory
            form_login:
                check_path: /admin/login/process
                login_path: /admin/login
                default_target_path: /admin/dashboard
            logout:
                path:   /admin/logout
                target: /

        site:
            pattern:    ^/
            provider: entity
            anonymous:  ~
            form_login:
                check_path: /user/login/process
                login_path: /user/login
                default_target_path: /user
            logout:
                path:   /user/logout
                target: /

    access_control:
        - { path: ^/user/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/user, roles: ROLE_USER }
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
4

7 回答 7

109

是的,您可以通过类似于以下内容的方式执行此操作:

use Symfony\Component\EventDispatcher\EventDispatcher,
    Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken,
    Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

public function registerAction()
{
    // ...
    if ($this->get("request")->getMethod() == "POST")
    {
        // ... Do any password setting here etc

        $em->persist($user);
        $em->flush();

        // Here, "public" is the name of the firewall in your security.yml
        $token = new UsernamePasswordToken($user, $user->getPassword(), "public", $user->getRoles());

        // For older versions of Symfony, use security.context here
        $this->get("security.token_storage")->setToken($token);

        // Fire the login event
        // Logging the user in above the way we do it doesn't do this automatically
        $event = new InteractiveLoginEvent($request, $token);
        $this->get("event_dispatcher")->dispatch("security.interactive_login", $event);

        // maybe redirect out here
    }
}

当您将令牌设置到上下文中时,最后触发的事件不会自动完成,而通常在使用登录表单或类似表单时会发生。因此,将其包含在此处的原因。您可能需要调整使用的令牌类型,具体取决于您的用例 -UsernamePasswordToken上面显示的是核心令牌,但如果需要,您可以使用其他令牌。

编辑:根据 Franco 在下面的评论,调整了上述代码以解释“公共”参数,并将用户的角色添加到令牌创建中。

于 2012-03-03T22:16:15.590 回答
4

接受的版本不适用于 symfony 3.3。用户将在下一个请求而不是当前请求中进行身份验证。原因是 ContextListener 检查以前的会话是否存在,如果不存在,它将清除安全 TokenStorage。解决这个问题的唯一方法(骇人听闻)是通过在当前请求上手动初始化会话(和 cookie)来伪造先前会话的存在。

如果您找到更好的解决方案,请告诉我。

顺便说一句,我不确定这是否应该与接受的解决方案合并。

private function logUserIn(User $user)
{
    $token = new UsernamePasswordToken($user, null, "common", $user->getRoles());
    $request = $this->requestStack->getMasterRequest();

    if (!$request->hasPreviousSession()) {
        $request->setSession($this->session);
        $request->getSession()->start();
        $request->cookies->set($request->getSession()->getName(), $request->getSession()->getId());
    }

    $this->tokenStorage->setToken($token);
    $this->session->set('_security_common', serialize($token));

    $event = new InteractiveLoginEvent($this->requestStack->getMasterRequest(), $token);
    $this->eventDispatcher->dispatch("security.interactive_login", $event);
}

上面的代码假定您的防火墙名称(或共享上下文名称)是common.

于 2017-07-17T15:18:12.937 回答
3

试试这个:对于 Symfony 3 用户,不要忘记进行此更正以测试密码的相等性(因为在此链接上显示的测试密码的方法不起作用):

$current_password = $user->getPassword();
$user_entry_password = '_got_from_a_form';

$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user_entry_password, $user->getSalt());

if(hash_equals($current_password, $password)){
//Continue there
}

// I hash the equality process for more security

+ 信息:hash_equals_function

于 2017-10-22T17:45:45.990 回答
2

对于 symfony 6,以下工作:

public function login(User $user, Request $request, UserCheckerInterface $checker, UserAuthenticatorInterface $userAuthenticator, FormLoginAuthenticator $formLoginAuthenticator): void
{
    $checker->checkPreAuth($user);
    $userAuthenticator->authenticateUser($user, $formLoginAuthenticator, $request);
}

您可以选择将此功能移动到服务中,以便更容易进行依赖注入:

# config/services.yaml

services:
    App\Service\LoginService:
        arguments:
            $formLoginAuthenticator: '@security.authenticator.form_login.main'
# src/Service/LoginService.php

namespace App\Service;

use App\Entity\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;

class LoginService
{
    private UserCheckerInterface $checker;
    private UserAuthenticatorInterface $userAuthenticator;
    private FormLoginAuthenticator $formLoginAuthenticator;

    /**
     * @param UserCheckerInterface $checker
     * @param UserAuthenticatorInterface $userAuthenticator
     * @param FormLoginAuthenticator $formLoginAuthenticator
     */
    public function __construct(UserCheckerInterface $checker, UserAuthenticatorInterface $userAuthenticator, FormLoginAuthenticator $formLoginAuthenticator)
    {
        $this->checker = $checker;
        $this->userAuthenticator = $userAuthenticator;
        $this->formLoginAuthenticator = $formLoginAuthenticator;
    }


    public function login(User $user, Request $request): void
    {
        $this->checker->checkPreAuth($user);
        $this->userAuthenticator->authenticateUser($user, $this->formLoginAuthenticator, $request);
    }
}

Source 是一个RFC,要求以更简单的方式进行编程登录。这目前正在实施中,可能会在 6.1 中登陆。

于 2022-02-04T14:12:18.580 回答
1

对于 Symfony 5,您可以使用开箱即用的功能来创建登录和注册表单。

使用 Symfony\Component\Security\Guard\GuardAuthenticatorHandler 是关键。

注册成功后可以在注册控制器中使用GuardAuthenticatorHandler。它登录用户并从 LoginFormAuthenticator 重定向到 onAuthenticationSuccess 中定义的页面。

下面,我添加了一些代码片段。

<?php

namespace App\Controller\Login;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class LoginController extends AbstractController
{
    /**
     * @Route("/login", name="app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {     
        // 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.');
    }
}

<?php

namespace App\Security;

use App\Entity\User\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    use TargetPathTrait;

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function getPassword($credentials): ?string
    {
        return $credentials['password'];
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return new RedirectResponse($this->urlGenerator->generate('app_homepage'));

//        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
//            return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
//        }
//
//        // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
//        throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate('app_login');
    }
}

<?php

namespace App\Controller;

use App\Entity\User\User;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;

class RegistrationController extends AbstractController
{
    private EntityManagerInterface $objectManager;

    private UserPasswordEncoderInterface $passwordEncoder;

    private GuardAuthenticatorHandler $guardHandler;

    private LoginFormAuthenticator $authenticator;

    /**
     * RegistrationController constructor.
     * @param EntityManagerInterface $objectManager
     * @param UserPasswordEncoderInterface $passwordEncoder
     * @param GuardAuthenticatorHandler $guardHandler
     * @param LoginFormAuthenticator $authenticator
     */
    public function __construct(
        EntityManagerInterface $objectManager,
        UserPasswordEncoderInterface $passwordEncoder,
        GuardAuthenticatorHandler $guardHandler,
        LoginFormAuthenticator $authenticator
    ) {
        $this->objectManager = $objectManager;
        $this->passwordEncoder = $passwordEncoder;
        $this->guardHandler = $guardHandler;
        $this->authenticator = $authenticator;
    }

    /**
     * @Route("/registration")
     */
    public function displayRegistrationPage()
    {
        return $this->render(
            'registration/registration.html.twig',
            );
    }

    /**
     * @Route("/register", name="app_register")
     *
     * @param Request $request
     * @return Response
     */
    public function register(Request $request)
    {
//        if (!$this->isCsrfTokenValid('sth-special', $request->request->get('token'))) {
//            return $this->render(
//                'registration/registration.html.twig',
//                ['errorMessage' => 'Token is invalid']
//            );
//        }

        $user = new User();
        $user->setEmail($request->request->get('email'));
        $user->setPassword(
            $this->passwordEncoder->encodePassword(
                $user,
                $request->request->get('password')
            )
        );
        $user->setRoles(['ROLE_USER']);

        $this->objectManager->persist($user);
        $this->objectManager->flush();

        return $this->guardHandler->authenticateUserAndHandleSuccess(
            $user,
            $request,
            $this->authenticator,
            'main' // firewall name in security.yaml
        );

        return $this->render('base.html.twig');
    }
}

于 2020-04-22T21:15:53.887 回答
0

经过几天的调试和调查,我终于在 Symfony 4.4 上以编程方式验证了一个用户。我想这种方法也应该适用于较新的版本。

在我的情况下,获取正确的防火墙名称很重要main,在您的security.yml

security:
    firewalls:
        main:
            pattern: ^/
            #...

然后将其传递到会话中:

$session->set('_security_main', serialize($token));

登录操作的完整代码:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
//...

public function loginAction(
    Request $request,
    TokenStorageInterface $tokenStorage,
    SessionAuthenticationStrategyInterface $sessionStrategy,
    AuthenticationProviderManager $authManager
) {
    // ...
    if ($request->getMethod() == "POST") {
        // Fetching user and checking password logic...
        $em->flush();

        // Create an authenticated token for the User.
        // Here, "main" is the name of the firewall in your security.yml
        $token = new UsernamePasswordToken(
            $email,
            $password,
            'main', // firewall name in security.yaml
            $user->getRoles()
        );

        $session = $request->getSession();
        if (!$request->hasPreviousSession()) {
            $request->setSession($session);
            $request->getSession()->start();
            $request->cookies->set($request->getSession()->getName(), $request->getSession()->getId());
        }

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

        // Authenticate user
        $authManager->authenticate($token);
        $sessionStrategy->onAuthentication($request, $token);

        // For older versions of Symfony, use "security.context" here
        $tokenStorage->setToken($token);
        $session->set('_security_main', serialize($token));

        $session->remove(Security::AUTHENTICATION_ERROR);
        $session->remove(Security::LAST_USERNAME);

        // Fire the login event
        $event = new InteractiveLoginEvent($request, $token);
        $this->get('event_dispatcher')->dispatch($event, SecurityEvents::INTERACTIVE_LOGIN);

        // return success response here
    }
}
于 2021-04-14T11:03:25.167 回答
0
$this->get('fos_user.security.login_manager')->logInUser('main', $user);

'main'你的防火墙的名字在哪里security.yml$user是代表你要登录的用户的对象。

这适用于我的 Symfony 2.8 项目,您可以通过运行来检查您的版本中的 login_manager 服务php app/console debug:container

于 2021-11-03T19:31:21.727 回答