Автоматическая аутентификация пользователя после регистрации
Мы создаем бизнес-приложение с нуля в Symfony 2, и я столкнулся с небольшим затруднением в процессе регистрации пользователей: после того, как пользователь создает учетную запись, он должен автоматически войти в систему с этими учетными данными, вместо этого быть немедленно вынужденным предоставить свои полномочия снова.
Кто-нибудь имел опыт работы с этим или мог указать мне правильное направление?
8 ответов
Symfony 4.0
Этот процесс не изменился с Symfony 3 на 4, но вот пример использования недавно рекомендованного AbstractController. Оба security.token_storage
и session
услуги зарегистрированы в родительской getSubscribedServices
метод, так что вам не нужно добавлять их в свой контроллер.
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use YourNameSpace\UserBundle\Entity\User;
class LoginController extends AbstractController{
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->container->get('security.token_storage')->setToken($token);
$this->container->get('session')->set('_security_main', serialize($token));
// The user is now logged in, you can redirect or do whatever.
}
}
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 параметра: сущность пользователя, учетные данные пользователя, имя брандмауэра, роли пользователя. Вам не нужно предоставлять учетные данные пользователя, чтобы токен был действительным.
Я не уверен на 100%, что установка токена на security.context
необходимо, если вы просто собираетесь перенаправить прямо сейчас. Но, похоже, это не больно, поэтому я оставил это.
Затем важная часть, установка переменной сеанса. Соглашение об именовании переменных _security_
затем имя вашего брандмауэра, в этом случае main
изготовление _security_main
Наконец-то понял это.
После регистрации пользователя у вас должен быть доступ к экземпляру объекта, который вы указали в качестве пользовательского объекта в конфигурации вашего провайдера. Решение состоит в том, чтобы создать новый токен с этим пользовательским объектом и передать его в контекст безопасности. Вот пример, основанный на моей настройке:
RegistrationController.php:
$token = new UsernamePasswordToken($userEntity, null, 'main', array('ROLE_USER'));
$this->get('security.context')->setToken($token);
куда main
это имя брандмауэра для вашего приложения (спасибо, @Joe). Это действительно все, что нужно сделать; система теперь считает, что ваш пользователь полностью вошел в систему как пользователь, которого он только что создал.
РЕДАКТИРОВАТЬ: В соответствии с комментарием @Miquel, я обновил образец кода контроллера, чтобы включить разумную роль по умолчанию для нового пользователя (хотя, очевидно, это может быть скорректировано в соответствии с конкретными потребностями вашего приложения).
Если у вас есть объект UserInterface (и так должно быть в большинстве случаев), вы можете использовать функцию getRoles, которую он реализует для последнего аргумента. Так что если вы создаете функцию logUser, она должна выглядеть так:
public function logUser(UserInterface $user) {
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->container->get('security.context')->setToken($token);
}
Я использую Symfony 2.2, и мой опыт немного отличался от проблематического, так что это комбинированная версия всей информации из этого вопроса плюс часть моего собственного.
Я думаю, что Джо не прав насчет ценности $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
, Кроме того, использование надлежащего поставщика будет означать, что события были обработаны для вас.
В Symfony 4.4 вы можете просто сделать следующее в своем методе контроллера (см. Документацию Symfony: https://symfony.com/doc/current/security/guard_authentication.html):
// src/Controller/RegistrationController.php
// ...
use App\Security\LoginFormAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
class RegistrationController extends AbstractController
{
public function register(LoginFormAuthenticator $authenticator, GuardAuthenticatorHandler $guardHandler, Request $request)
{
// ...
// after validating the user and saving them to the database
// authenticate the user and use onAuthenticationSuccess on the authenticator
return $guardHandler->authenticateUserAndHandleSuccess(
$user, // the User object you just created
$request,
$authenticator, // authenticator whose onAuthenticationSuccess you want to use
'main' // the name of your firewall in security.yaml
);
}
}
Одна важная вещь: убедитесь, что ваш брандмауэр не настроен на lazy
. Если это так, токен никогда не будет сохранен в сеансе, и вы никогда не войдете в систему.
firewalls:
main:
anonymous: ~ # this and not 'lazy'
На тот случай, если у кого-то возникнет такой же вопрос, который заставил меня вернуться сюда:
призвание
$this->container->get('security.context')->setToken($token);
влияет только на текущий security.context
для использованного маршрута.
Т.е. вы можете войти в систему только с URL-адреса, находящейся под контролем брандмауэра.
(При необходимости добавьте исключение для маршрута - IS_AUTHENTICATED_ANONYMOUSLY
)
Я попробовал все ответы здесь, и ни один не работал. Единственный способ, которым я мог аутентифицировать своих пользователей на контроллере, это сделать подзапрос, а затем перенаправить. Вот мой код, я использую 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'));
Как уже упоминалось в разделе "Проблема", этот неуловимый параметр $providerKey в действительности является не чем иным, как именем вашего правила брандмауэра, "foobar" в случае примера ниже.
firewalls:
foobar:
pattern: /foo/
В 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
(из версии FOSUserBundle composer.json: "friendsofsymfony/user-bundle": "~1.3")