自动注册后用户认证


114

我们正在Symfony 2中从头开始构建一个业务应用程序,但是我对用户注册流程有点困扰:用户创建帐户后,应该使用这些凭据自动登录。立即被迫再次提供其凭据。

任何人都对此有任何经验,或者能够为我指明正确的方向?

Answers:


146

Symfony 4.0

此过程尚未从symfony 3更改为4,但这是使用新推荐的AbstractController的示例。无论是security.token_storagesession服务注册在父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个参数:用户实体,用户凭据,防火墙名称,用户角色。您无需提供用户凭据即可使令牌有效。

security.context如果您要立即重定向,我不是100%确定必须在上设置令牌。但这似乎没有伤害,所以我离开了。

然后是重要的部分,设置会话变量。变量命名约定_security_后跟您的防火墙名称,在这种情况下main_security_main


1
我已经实现了代码,用户已成功登录,但是$ this-> getUser()对象返回NULL。任何想法?
sathish 2014年

2
没有发生疯狂的事情$this->get('session')->set('_security_main', serialize($token));。谢谢@Chausser!
Dmitriy 2014年

1
在Symfony 2.6中,如果您为名为mainAND 的防火墙设置令牌,那么您将通过另一个名为防火墙的身份验证admin(因为您要冒充用户),则发生了一件奇怪的事情:_security_admin获取UsernamePasswordToken与您提供的用户的,即您与您的admin防火墙。任何想法如何维护“管理员”防火墙的令牌?
gremo 2015年

1
老实说,我不确定您是否可以同时对2个防火墙进行身份验证,请仔细检查一下,但与此同时,您应该提出一个单独的问题
Chase

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'5

@maectpo不在我的原始要求范围内,但是听起来像是一个很好的后续答案。让我们知道您的想法。
问题的2012年

我有一个问题。我可以用这种方式登录,但是app.user变量为空。您是否知道在此登录过程中填充此变量的任何方法?-我发送用户(字符串)和密码(字符串),例如引用:api.symfony.com/2.0/Symfony/Component/Security/Core/…–
unairoldan

1
就像下面的Marc所说,您需要注册UsernamePasswordToken命名空间:use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
MrGlass 2012年

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的经验略有不同,因此这是该问题的所有信息加上我自己的一些信息的组合版本。

我认为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

如果有人有相同的后续问题,让我回到这里:

呼唤

$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的原因吗?这个参数是什么意思?
米哈伊尔(Mikhail)2012年

1
此参数将您的令牌绑定到特定的防火墙提供程序。在大多数情况下,您只有一个提供程序,因此不必理会。
Gottlieb Notschnabel 2015年

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

就像我在其他解决方案中看到的那样,无需调度事件。

从FOS \ UserBundle \ Controller \ RegistrationController :: authenticateUser注入

(来自composer.json FOSUserBundle版本:“ friendsofsymfony / user-bundle”:“〜1.3”)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.