Автоматическая аутентификация пользователя после регистрации

Мы создаем бизнес-приложение с нуля в Symfony 2, и я столкнулся с некоторой проблемой с потоком регистрации пользователей: после того, как пользователь создал учетную запись, они должны автоматически войти в систему с этими учетных данных, вместо того, чтобы сразу же принудительно предоставить свои учетные данные снова.

У кого-нибудь был опыт с этим, или он мог указать мне в правильном направлении?

Ответ 1

Symfony 2.6.x - Symfony 3.0.x

Как и в случае с symfony 2.6 security.context, не рекомендуется использовать security.token_storage. Теперь контроллер может просто быть:

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use YourNameSpace\UserBundle\Entity\User;

class LoginController extends Controller{

    public function registerAction()
    {    
        $user = //Handle getting or creating the user entity likely with a posted form
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->get('security.token_storage')->setToken($token);
        $this->get('session')->set('_security_main', serialize($token));
    }

}

В то время как это устарело, вы все равно можете использовать security.context, поскольку это было сделано для обратной совместимости. Просто будьте готовы обновить его для Symfony 3

Подробнее об изменениях 2.6 для безопасности вы можете прочитать здесь: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md

Symfony 2.3.x

Чтобы выполнить это в symfony 2.3, вы уже не можете просто установить токен в контексте безопасности. Вам также нужно сохранить токен в сеансе.

Предположим, файл безопасности с брандмауэром:

// app/config/security.yml
security:
    firewalls:
        main:
            //firewall settings here

И подобное действие контроллера тоже:

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use YourNameSpace\UserBundle\Entity\User;

class LoginController extends Controller{

    public function registerAction()
    {    
        $user = //Handle getting or creating the user entity likely with a posted form
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->get('security.context')->setToken($token);
        $this->get('session')->set('_security_main',serialize($token));
        //Now you can redirect where ever you need and the user will be logged in
    }

}

Для создания маркера вы захотите создать UsernamePasswordToken, который принимает 4 параметра: Пользовательский объект, Пользовательские учетные данные, Имя брандмауэра, Роли пользователей. Вам не нужно предоставлять учетные данные пользователя, чтобы токен был действительным.

Im не на 100% уверен, что установка маркера на security.context необходима, если вы просто собираетесь перенаправить сразу. Но, похоже, он не пострадал, поэтому я оставил его.

Затем важная часть, задающая переменную сеанса. Соглашение об именах переменных указано _security_, за которым следует ваше имя брандмауэра, в этом случае main делает _security_main

Ответ 2

Наконец-то выложите это.

После регистрации пользователя вы должны иметь доступ к объекту instanceof того, что вы установили в качестве своего пользовательского объекта в настройках вашего провайдера. Решение состоит в том, чтобы создать новый токен с этим пользовательским объектом и передать его в контекст безопасности. Вот пример, основанный на моей настройке:

RegistrationController.php:

$token = new UsernamePasswordToken($userEntity, null, 'main', array('ROLE_USER'));
$this->get('security.context')->setToken($token);

Где main - имя брандмауэра для вашего приложения (спасибо @Joe). Это действительно все для этого; система теперь считает, что ваш пользователь полностью зарегистрирован как пользователь, которого только что создал.

EDIT: за комментарий @Miquel, я обновил образец кода контроллера, чтобы включить разумную роль по умолчанию для нового пользователя (хотя, очевидно, это можно настроить в соответствии с вашими конкретными потребностями).

Ответ 3

Я использую Symfony 2.2, и мой опыт немного отличался от Проблемных, так что это комбинированная версия всей информации из этого вопроса плюс некоторые из моих собственных,

Я думаю, что Joe неверен относительно значения $providerKey, третьего параметра для конструктора UsernamePasswordToken. Он должен быть ключом провайдера аутентификации (не пользователя). Он используется системой аутентификации для различения токенов, созданных для разных поставщиков. Любой поставщик, который спускается из UserAuthenticationProvider, будет только проверять токены, чей ключ поставщика соответствует его собственному. Например, UsernamePasswordFormAuthenticationListener устанавливает ключ маркера, который он создает, чтобы соответствовать его соответствующему DaoAuthenticationProvider. Это позволяет одному брандмауэру иметь несколько провайдеров имени пользователя и пароля без их перехода друг на друга. Поэтому нам нужно выбрать ключ, который не будет конфликтовать с другими поставщиками. Я использую 'new_user'.

У меня есть несколько систем в других частях моего приложения, которые зависят от события успешной проверки подлинности, и это не срабатывает просто установка токена в контексте. Я должен был получить EventDispatcher из контейнера и запустить событие вручную. Я также решил не запускать интерактивное событие входа, потому что мы аутентифицируем пользователя неявно, а не в ответ на явный запрос на вход.

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;

$user = // get a Symfony user instance somehow
$token = new UsernamePasswordToken(
        $user, null, 'new_user', $user->getRoles() );
$this->get( 'security.context' )->setToken( $token );
$this->get( 'event_dispatcher' )->dispatch(
        AuthenticationEvents::AUTHENTICATION_SUCCESS,
        new AuthenticationEvent( $token ) );

Обратите внимание, что использование $this->get( .. ) предполагает, что фрагмент находится в методе контроллера. Если вы используете код где-то еще, вам придется изменить их, чтобы вызвать ContainerInterface::get( ... ) таким образом, который подходит для среды. Так как это происходит, мои пользовательские объекты реализуют UserInterface, поэтому я могу использовать их непосредственно с токеном. Если вы этого не сделаете, вам придется найти способ конвертировать их в экземпляры UserInterface.

Этот код работает, но я чувствую, что он взламывает архитектуру аутентификации Symfony, а не работает с ней. Вероятно, было бы правильнее внедрить новый поставщик аутентификации со своим собственным классом токенов, а не захватить UsernamePasswordToken. Кроме того, использование надлежащего провайдера означает, что события были обработаны для вас.

Ответ 4

Если у вас есть объект UserInterface (и это должно быть в большинстве случаев), вы можете использовать функцию getRoles, которую она реализует для последнего аргумента. Поэтому, если вы создаете функцию logUser, она должна выглядеть так:

public function logUser(UserInterface $user) {
    $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
    $this->container->get('security.context')->setToken($token);
}

Ответ 5

В случае, если у кого-то есть один и тот же следующий вопрос, который заставлял меня возвращаться сюда:

Вызов

$this->container->get('security.context')->setToken($token); 

влияет только на текущий security.context для используемого маршрута.

т.е. вы можете только войти в систему пользователя с URL-адреса в элементе управления брандмауэром.

(добавьте исключение для маршрута, если необходимо - IS_AUTHENTICATED_ANONYMOUSLY)

Ответ 6

Как уже упоминалось, этот неуловимый параметр $providerKey на самом деле является не чем иным, как именем вашего правила брандмауэра "foobar" в примере ниже.

firewalls:
    foobar:
        pattern:    /foo/

Ответ 7

Я попробовал все ответы здесь, и никто не работал. Единственный способ, которым я мог аутентифицировать своих пользователей на контроллере, - это сделать подзапрос, а затем перенаправить. Вот мой код, я использую silex, но вы можете легко адаптировать его к symfony2:

$subRequest = Request::create($app['url_generator']->generate('login_check'), 'POST', array('_username' => $email, '_password' => $password, $request->cookies->all(), array(), $request->server->all());

$response = $app->handle($subRequest, HttpKernelInterface::MASTER_REQUEST, false);

return $app->redirect($app['url_generator']->generate('curriculos.editar'));

Ответ 8

В Symfony версии 2.8.11 (возможно, для старых и новых версий) , если вы используете FOSUserBundle, просто выполните следующее:

try {
    $this->container->get('fos_user.security.login_manager')->loginUser(
    $this->container->getParameter('fos_user.firewall_name'), $user, null);
} catch (AccountStatusException $ex) {
    // We simply do not authenticate users which do not pass the user
    // checker (not enabled, expired, etc.).
}

Не нужно отправлять события, как я видел в других решениях.

вызванный из FOS\UserBundle\Controller\RegistrationController:: authenticateUser

(from composer.json FOSUserBundle версия: "friendsofsymfony/user-bundle": "~ 1.3" )