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

114

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

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

проблематичный
источник

Ответы:

146

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

гнаться
источник
1
Я реализовал код, пользователь успешно вошел в систему, но объект $ this-> getUser () возвращает NULL. Любая идея?
sathish
2
Безумные вещи происходили без него $this->get('session')->set('_security_main', serialize($token));. Спасибо, @Chausser!
Дмитрий
1
В Symfony 2.6, если вы устанавливаете токен для брандмауэра с именем mainИ вы аутентифицируетесь с другим названным брандмауэром admin(так как вы выдаете себя за пользователя), происходит странная вещь: он _security_adminполучает сообщение UsernamePasswordTokenс пользователем, которого вы указали, т.е. вы "отключаетесь" от ваш adminбрандмауэр. Есть идеи, как сохранить токен для брандмауэра «администратора»?
gremo
1
Честно говоря, я не уверен, что вы можете быть аутентифицированы для двух брандмауэров одновременно, я плохо разбираюсь в этом, но тем временем вам следует задать отдельный вопрос
Чейз
3
@Chausser удалось заставить его работать. Ваш ответ совершенно правильный (и он обновлен), единственное, что он работает, только когда вы звоните setToken(..)под тем же целевым брандмауэром или еще не прошли аутентификацию .
gremo
65

Наконец-то понял это.

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

RegistrationController.php:

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

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

РЕДАКТИРОВАТЬ: за комментарий @Miquel, я обновил образец кода контроллера, чтобы включить разумную роль по умолчанию для нового пользователя (хотя, очевидно, это можно настроить в соответствии с конкретными потребностями вашего приложения).

проблематичный
источник
2
Это не совсем правильно с релизной версией Symfony 2. Вам необходимо передать роли пользователя в качестве четвертого аргумента конструктору UsernamePasswordToken, иначе он будет отмечен как не прошедший аутентификацию, и у пользователя не будет ни одной из своих ролей.
Майкл
А как насчет флага «Запомнить меня»? Как авторизовать пользователей вручную, но они также должны быть авторизованы навсегда. Этот фрагмент кода не решает эту проблему.
maectpo
@maectpo, который не входил в мои первоначальные требования, но звучит как отличный ответ. Сообщите нам, что вы придумали.
Проблемная
У меня проблема. Я могу войти таким образом, но переменная app.user пуста. Вы знаете, как заполнить эту переменную в этом процессе входа в систему? - Я отправляю пользователя (строку) и пароль (строку), как сказано в Справке: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan
1
Как сказал Марк ниже, вам необходимо зарегистрировать пространство имен UsernamePasswordToken:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass
6

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

public function logUser(UserInterface $user) {
    $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
    $this->container->get('security.context')->setToken($token);
}
Седрик Нируссе
источник
6

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

Я думаю, что Джо ошибается относительно значения $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

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

призвание

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

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

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

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

демон
источник
1
вы знаете, как вы это делаете для сеанса? А не только для текущего запроса?
Jake N
3

В Symfony 4.4 вы можете просто сделать следующее в своем методе контроллера (см. Документацию Symfony: https://symfony.com/doc/current/security/guard_authentication.html#manually-authenticating-a-user ):

// 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'
Этьен Ноэль
источник
2

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

firewalls:
    foobar:
        pattern:    /foo/
Nim
источник
Можете ли вы объяснить мне, почему, если я передам любую строку, например, в blablablaкачестве третьего параметра в UsernamePasswordToken, она тоже будет работать? что означает этот параметр?
Михаил
1
Этот параметр привязывает ваш токен к определенному поставщику брандмауэра. В большинстве случаев у вас будет только один провайдер, поэтому не беспокойтесь об этом.
Gottlieb Notschnabel
2

Я попробовал все ответы здесь, и ни один из них не сработал. Единственный способ аутентифицировать своих пользователей на контроллере - это сделать подзапрос и затем перенаправить. Вот мой код, я использую silx, но вы можете легко адаптировать его к 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'));
Диего Кастро
источник
1

В 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

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

Нико
источник