我们正在 Symfony 2 中从头开始构建一个商业应用程序,我在用户注册流程中遇到了一些障碍:用户创建帐户后,他们应该使用这些凭据自动登录,而不是立即被迫再次提供他们的凭据。
任何人都有这方面的经验,或者能够指出我正确的方向?
我们正在 Symfony 2 中从头开始构建一个商业应用程序,我在用户注册流程中遇到了一些障碍:用户创建帐户后,他们应该使用这些凭据自动登录,而不是立即被迫再次提供他们的凭据。
任何人都有这方面的经验,或者能够指出我正确的方向?
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.6security.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.
终于想通了这个
用户注册后,您应该可以访问您在提供者配置中设置为用户实体的任何对象实例。解决方案是使用该用户实体创建一个新令牌并将其传递到安全上下文中。这是基于我的设置的示例:
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,我的体验与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. 此外,使用适当的提供者意味着事件已为您处理。
使用 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'
如果有人有同样的后续问题让我回到这里:
打电话
$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
(来自 composer.json FOSUserBundle 版本:“friendsofsymfony/user-bundle”:“~1.3”)