自动后注册用户认证

我们正在 Symfony 2中从头开始构建一个商业应用程序,我在用户注册流程中遇到了一点障碍: 用户创建一个帐户后,他们应该自动用这些凭证登录,而不是立即被迫再次提供他们的凭证。

有人有这方面的经验吗,或者能给我指明正确的方向吗?

35579 次浏览

终于想明白了。

After user registration, you should have access to an object instanceof whatever you've set as your user entity in your provider configuration. The solution is to create a new token with that user entity and pass it into the security context. Here's an example based on my setup:

RegistrationController.php:

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

其中 main是应用程序的防火墙名称(谢谢@Joe)。这就是它的全部内容; 系统现在认为您的用户完全登录为他们刚刚创建的用户。

编辑: 根据@Miquel 的评论,我已经更新了控制器代码示例,为新用户添加了一个合理的默认角色(尽管显然这可以根据应用程序的特定需求进行调整)。

正如这里已经提到的问题,这个难以捉摸的 $ProviderKey 参数实际上只不过是防火墙规则的名称,在下面的例子中是‘ foobar’。

firewalls:
foobar:
pattern:    /foo/

如果您有一个 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

I.e. you can only log in a user from a url within the firewall's control.

(如果需要,为路由添加一个异常 -IS_AUTHENTICATED_ANONYMOUSLY)

我使用的是 Symfony 2.2,我的体验与 有问题的略有不同,所以这是一个综合版本的所有信息从这个问题加上我自己的一些。

我认为 关于 $providerKey的值是错误的,$providerKeyUsernamePasswordToken构造函数的第三个参数。它应该是身份验证(而不是用户)提供程序的密钥。身份验证系统使用它来区分为不同提供程序创建的令牌。从 UserAuthenticationProvider继承的任何提供程序将只验证其提供程序密钥与自己的密钥匹配的令牌。例如,UsernamePasswordFormAuthenticationListener设置它创建的令牌的密钥以匹配其对应的 DaoAuthenticationProvider。这使得单个防火墙具有多个用户名 + 密码提供程序,而不会相互干扰。因此,我们需要选择一个不会与其他提供程序冲突的密钥。我用的是 'new_user'

I have a few systems in other parts of my application that depend on the 认证成功事件, and that isn't fired by just setting the token on the context. I had to get the EventDispatcher from the container and fire the event manually. I decided against also firing an 交互式登录事件 because we're authenticating the user implicitly, not in response to an explicit login request.

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 ) );

Note that use of $this->get( .. ) assumes the snippet is in a controller method. If you're using the code somewhere else you'll have to change those to call ContainerInterface::get( ... ) in a way appropriate to the environment. As it happens my user entities implement UserInterface so I can use them directly with the token. If yours don't you'll have to find a way to convert them to UserInterface instances.

这个代码有用,但我觉得它是在破解 Symfony 的认证架构,而不是在与之合作。与劫持 UsernamePasswordToken相比,使用 实现新的身份验证提供程序自己的标记类可能更为正确。另外,使用适当的提供程序将意味着事件已经为您处理。

I tried all the answers here and none worked. The only way I could authenticate my users on a controller is by making a subrequest and then redirecting. Here is my code, I'm using silex but you can easily adapt it to 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 4.0

这个过程没有从 Symfony 3改为 Symfony 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个参数: 用户实体,用户凭据,防火墙名称,用户角色。您不需要为令牌提供有效的用户凭据。

我不是100% 肯定设置令牌的 security.context是必要的,如果你只是要重定向立即。但似乎不疼,所以我离开了它。

然后是重要的部分,设置会话变量。变量变数命名原则是 _security_后面跟着你的防火墙名字,在这个例子中是 main变成 _security_main

在 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: : enticateUser

(来自 poser.json FOSUserBundle 版本: “ friends sofsymfony/user-bundle”: “ ~ 1.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'