Автоматична аутентифікація користувача після реєстрації


114

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

Хтось мав з цим досвід чи міг вказати мені в правильному напрямку?

Відповіді:


146

Symfony 4.0

Цей процес не змінився з символічного 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. Будь-яка ідея?
сатиш

2
Божевільні речі відбувалися без $this->get('session')->set('_security_main', serialize($token));. Дякую, @Chausser!
Дмитро

1
Якщо в Symfony 2.6 встановлено маркер для брандмауера з назвою mainAND і ви автентифіковані іншим брандмауером з назвою admin(як ви себе представляєте користувачеві), трапляється дивна річ: _security_adminотримує програму UsernamePasswordTokenз користувачем, якого ви надали, тобто ви отримуєте "відключений" від ваш adminбрандмауер. Будь-яка ідея, як підтримувати маркер для брандмауера "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). Це дійсно все, що там є; тепер система вважає, що ваш користувач повністю увійшов до системи, як щойно створений користувач.

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


2
Це не зовсім правильно з версією випуску Symfony 2. Вам потрібно передати ролі користувача в якості четвертого аргументу конструктору UsernamePasswordToken, інакше він буде позначений як неаутентифікований, і користувач не матиме жодної своєї ролі.
Майкл

А що з прапором "Запам'ятай мене"? Як входити в систему вручну користувачів, але також вони повинні бути увійшли довічно. Цей фрагмент коду не вирішує цю проблему.
maectpo

@maectpo, який не входив до моїх початкових вимог, але звучить як чудова відповідь. Дайте нам знати, що ви придумали.
Проблемні

У мене є проблема. Я можу ввійти таким чином, але змінна app.user порожня. Чи знаєте ви який-небудь спосіб заповнити цю змінну в цьому процесі входу? - Я надсилаю користувачеві (рядок) і пароль (рядок), як кажуть Довідка: api.symfony.com/2.0/Symfony/Component/Security/Core/…
unairoldan

1
Як сказав Марк нижче, вам потрібно зареєструвати простір імен ім'я користувачаPasswordToken: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, і мій досвід був дещо іншим, ніж Проблематичний , тому це комбінована версія всієї інформації з цього питання плюс частина моєї власної.

Я думаю, що Джо помиляється щодо значення $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
ти знаєш, як це робиш на сеансі? Замість цього лише поточного запиту?
Джейк 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

Як вже було згадано тут Проблематично, цей невловимий параметр $ providerKey насправді є не що інше, як назва вашого правила брандмауера "foobar" у випадку наведеного нижче прикладу.

firewalls:
    foobar:
        pattern:    /foo/

Чи можете ви пояснити мені, чому, якщо я передаю будь-який рядок, наприклад, blablablaяк третій параметр, в UsernamePasswordToken, він також буде працювати? що означає цей параметр?
Михайло

1
Цей параметр пов'язує ваш маркер із конкретним постачальником брандмауера. У більшості випадків у вас буде лише один постачальник, тому не турбуйтеся про це.
Gottlieb Notschnabel

2

Я спробував усі відповіді тут, і жодна не спрацювала. Єдиний спосіб я міг аутентифікувати своїх користувачів на контролері - це зробити підзапит і потім перенаправити. Ось мій код, я використовую 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'));

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.).
}

Не потрібно відправляти подію, як я бачив в інших рішеннях.

inpired from FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser

(від версії composer.json FOSUserBundle: "friendsofsymfony / user-bundle": "~ 1.3")

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.