Как программно войти в систему / аутентифицировать пользователя?

Я хочу войти в систему сразу после регистрации, не проходя через форму входа.

Это возможно ? Я нашел решение с 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 }

person rayfranco    schedule 03.03.2012    source источник
comment
Если вы не используете FOSUserBundle, какой пакет вы используете на самом деле?   -  person hakre    schedule 04.03.2012
comment
@hakre Я не использую никаких пакетов, а просто настраиваемый объект User, реализующий UserInterface.   -  person rayfranco    schedule 04.03.2012
comment
Добавьте вашу security: конфигурацию к вашему вопросу. Скрыть конфиденциальные значения.   -  person hakre    schedule 04.03.2012
comment
@hakre Я добавил свой файл security.yml. Я сейчас тестирую ответ Richsage.   -  person rayfranco    schedule 04.03.2012
comment
Возможный дубликат автоматической аутентификации пользователя после регистрации   -  person Chase    schedule 27.04.2016


Ответы (5)


Да, вы можете сделать это примерно так:

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, показанный выше, является основным токеном, но при необходимости вы можете использовать другие.

Изменить. В приведенном выше коде исправлен параметр "общедоступный", а также добавлены роли пользователя при создании токена на основе комментария Франко ниже.

person richsage    schedule 03.03.2012
comment
Спасибо за этот ответ. Кажется, это правильный путь, но на самом деле он не работает. Что касается моего последнего редактирования (security.yml), я изменил providerKey (где ваш является общедоступным для объекта), но я не уверен, что поступил правильно. Когда вы говорите, что вам может потребоваться изменить тип токена, я не уверен, что понимаю. Я искал здесь Спасибо за вашу помощь. - person rayfranco; 04.03.2012
comment
Я нашел справку в этой ветке и, наконец, нашел, что не так. Третий параметр - это имя брандмауэра, а четвертый параметр необходим, который представляет собой массив ролей для токена. У меня это сработало - person rayfranco; 04.03.2012
comment
@FrancoBouly, отличный материал, рад, что у вас все заработало! Я думаю, что этот параметр брандмауэра, возможно, потребуется отправить обратно в документацию, так как я не мог вспомнить, почему я сделал это изначально :-) Если только я не пропустил его полностью ;-) - person richsage; 04.03.2012
comment
Судя по названию, я не уверен, что увольнять это мероприятие - правильное решение. Интерактивное событие входа в систему, и это не интерактивный вход. Есть предположения? - person Amr Mostafa; 29.06.2012
comment
@AmrMostafa Я считаю, что есть лучший способ, который не использует событие, я посмотрю, смогу ли я его откопать и обновить свой ответ :-) - person richsage; 01.01.2013
comment
Этот пример от KNPlabs не требует запуска каких-либо событий и работает! knplabs.com/blog/redirect-after-registration-in-symfony2 - person Jekis; 11.11.2013
comment
Есть ли способ сохранить пользователя в системе (как если бы он / она установил флажок в форме входа)? Прямо сейчас, если мой сеанс истекает, я выхожу из системы - person totas; 19.02.2014
comment
$this->get("security.context") устарело, используйте $this->get('security.token_storage') - person moldcraft; 14.07.2015
comment
Это не будет работать должным образом с последней версией Symfony. Пользователь будет аутентифицирован в следующем запросе вместо текущего. Причина в том, что ContextListener проверяет наличие предыдущего сеанса и, если он не существует, очищает TokenStorage безопасности. Единственный способ обойти это (чертовски хакерский) - подделать существование предыдущего сеанса, вручную инициализировав сеанс (и файл cookie) для текущего запроса. - person pinkeen; 01.06.2017
comment
@pinkeen, спасибо за это - думаю, это было еще через 2,1 дня. Я добавлю комментарий к этому эффекту - вы знаете, с какой версии это перестало бы работать? (В последнее время я не занимался разработкой Symfony для чего-либо старше 2.7!) - person richsage; 02.06.2017
comment
На 2.2 и 2.3 у меня не заработало. Не знаю, как раньше. Позже проверю, когда делали капитальный ремонт системы безопасности. - person pinkeen; 04.06.2017
comment
@pinkeen может добавить пример для Symfony 3.3? Мне нужно войти в систему вручную - person Braian Mellor; 17.07.2017
comment
Похоже, это приводит к исчерпанию лимита памяти PHP в Symfony 2.8.32. - person Adambean; 18.12.2017

Принятая версия не будет работать с 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.

person pinkeen    schedule 17.07.2017
comment
Правильный способ - установить для параметра require_previous_session значение false в брандмауэре form_login: require_previous_session: false - person Krasimir Bosilkov; 25.09.2017
comment
Я бы должен проверить. Но я смутно помню, что пробовал это, и это не помогло. - person pinkeen; 26.09.2017

Попробуйте следующее: Для пользователей 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

person Bill Somen    schedule 22.10.2017

Для Symfony 5 вы можете использовать готовые функции для создания форм входа и регистрации.

Использование Symfony \ Component \ Security \ Guard \ GuardAuthenticatorHandler является ключевым моментом.

Вы можете использовать GuardAuthenticatorHandler в контроллере регистрации после успешной регистрации. Он входит в систему и перенаправляет пользователя на страницу, определенную в onAuthenticationSuccess, из LoginFormAuthenticator.

Ниже я добавил несколько фрагментов кода.

<?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');
    }
}

person mtwegrzycki    schedule 22.04.2020

После нескольких дней отладки и исследования я наконец программно аутентифицирую пользователя на 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
    }
}
person Serhii Popov    schedule 14.04.2021