Мы создаем бизнес-приложение с нуля в Symfony 2, и я столкнулся с некоторой проблемой с потоком регистрации пользователей: после того, как пользователь создал учетную запись, они должны автоматически войти в систему с этими учетными данными, вместо этого из-за того, что они были вынуждены снова предоставить свои полномочия.
У кого-нибудь был опыт с этим, или он мог указать мне в правильном направлении?
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 параметра: User Entity, User Credentials, имя брандмауэра, роли пользователей. Вам не нужно предоставлять учетные данные пользователя для того, чтобы токен был действительным.
Im не 100% уверен, что установка маркера в security.context
необходима, если вы просто собираетесь перенаправить сразу. Но, похоже, он не пострадал, поэтому я его оставил.
Затем важная часть, задающая переменную сеанса. Соглашение об именах переменных – _security_
за которым следует ваше имя брандмауэра, в этом случае main
делает _security_main
Выяснил это, наконец.
После регистрации пользователя вы должны иметь доступ к объекту instanceof того, что вы установили в качестве своего пользовательского объекта в конфигурации вашего поставщика. Решение состоит в том, чтобы создать новый токен с этим пользовательским объектом и передать его в контекст безопасности. Вот пример, основанный на моей настройке:
RegistrationController.php:
$token = new UsernamePasswordToken($userEntity, null, 'main', array('ROLE_USER')); $this->get('security.context')->setToken($token);
Где main
имя брандмауэра для вашего приложения (спасибо, @Joe). Это действительно все для этого; система теперь считает, что ваш пользователь полностью зарегистрирован как пользователь, которого они только что создали.
EDIT: комментарий Пер @ Микеля, я обновил образец кода контроллера, чтобы включить разумную роль по умолчанию для нового пользователя (хотя, очевидно, это можно настроить в соответствии с конкретными потребностями вашего приложения).
Я использую Symfony 2.2, и мой опыт немного отличался от проблемных , так что это комбинированная версия всей информации из этого вопроса плюс некоторые из моих собственных.
Я думаю, что Joe ошибается в отношении значения $providerKey
, третьего параметра конструктора UsernamePasswordToken
. Он должен быть ключом провайдера аутентификации (а не пользователя). Он используется системой аутентификации для различения токенов, созданных для разных поставщиков. Любой поставщик, который UserAuthenticationProvider
из 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
. Кроме того, использование надлежащего провайдера означает, что события были обработаны для вас.
Если у вас есть объект UserInterface (и это должно быть в большинстве случаев), вы можете использовать функцию getRoles, которую она реализует для последнего аргумента. Поэтому, если вы создаете функцию logUser, она должна выглядеть так:
public function logUser(UserInterface $user) { $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles()); $this->container->get('security.context')->setToken($token); }
В случае, если у кого-то есть один и тот же следующий вопрос, который заставлял меня возвращаться сюда:
призвание
$this->container->get('security.context')->setToken($token);
влияет только на текущий security.context
для используемого маршрута.
Т.е. вы можете только войти в систему пользователя с URL-адреса внутри элемента управления брандмауэром.
(При необходимости добавьте исключение для маршрута – IS_AUTHENTICATED_ANONYMOUSLY
)
Как уже упоминалось, этот неуловимый параметр $ providerKey на самом деле не что иное, как имя вашего правила брандмауэра «foobar» в примере ниже.
firewalls: foobar: pattern: /foo/
Я попробовал все ответы здесь, и никто не работал. Единственный способ, которым я мог аутентифицировать своих пользователей на контроллере, – это сделать подзапрос, а затем перенаправить. Вот мой код, я использую 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'));
В 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.). }
Не нужно отправлять события, как я видел в других решениях.
inpired из FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser
(от версии composer.json FOSUserBundle: «friendsofsymfony / user-bundle»: «~ 1.3»)