我想在注册过程后立即登录用户,而不通过登录表单。
这可能吗?我已经找到了
FOSUserBundle
的解决方案,但我没有在我实际正在进行的项目中使用它。
这是我的 security.yml,我正在使用两个防火墙。 纯文本编码器仅用于测试。
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
Ray\CentralBundle\Entity\Client: md5
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
users:
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
entity:
entity: { class: Ray\CentralBundle\Entity\Client, property: email }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
user_login:
pattern: ^/user/login$
anonymous: ~
admin_login:
pattern: ^/admin/login$
anonymous: ~
admin:
pattern: ^/admin
provider: in_memory
form_login:
check_path: /admin/login/process
login_path: /admin/login
default_target_path: /admin/dashboard
logout:
path: /admin/logout
target: /
site:
pattern: ^/
provider: entity
anonymous: ~
form_login:
check_path: /user/login/process
login_path: /user/login
default_target_path: /user
logout:
path: /user/logout
target: /
access_control:
- { path: ^/user/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
是的,您可以通过类似于以下内容的方式来完成此操作:
use Symfony\Component\EventDispatcher\EventDispatcher,
Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken,
Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
public function registerAction()
{
// ...
if ($this->get("request")->getMethod() == "POST")
{
// ... Do any password setting here etc
$em->persist($user);
$em->flush();
// Here, "public" is the name of the firewall in your security.yml
$token = new UsernamePasswordToken($user, $user->getPassword(), "public", $user->getRoles());
// For older versions of Symfony, use security.context here
$this->get("security.token_storage")->setToken($token);
// Fire the login event
// Logging the user in above the way we do it doesn't do this automatically
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
// maybe redirect out here
}
}
当您将令牌设置到上下文中时,最后触发的事件不会自动完成,而在使用登录表单或类似表单时通常会自动完成。这就是将其包含在这里的原因。您可能需要根据您的用例调整所使用的令牌类型 - 上面显示的
UsernamePasswordToken
是核心令牌,但如果需要,您可以使用其他令牌。
编辑:根据下面 Franco 的评论,调整了上面的代码以解释“public”参数,并将用户的角色添加到令牌创建中。
如果您使用的是 symfony ^6.2,则可以使用
Security::login()
。
对于旧版本(symfony ^5.4、^6.0、^6.1),可以执行以下操作:
public function login(User $user, Request $request, UserCheckerInterface $checker, UserAuthenticatorInterface $userAuthenticator, FormLoginAuthenticator $formLoginAuthenticator): void
{
$checker->checkPreAuth($user);
$userAuthenticator->authenticateUser($user, $formLoginAuthenticator, $request);
}
您可以选择将此功能移至服务中,以便更轻松地进行依赖项注入:
# config/services.yaml
services:
App\Service\LoginService:
arguments:
$formLoginAuthenticator: '@security.authenticator.form_login.main'
# src/Service/LoginService.php
namespace App\Service;
use App\Entity\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
class LoginService
{
private UserCheckerInterface $checker;
private UserAuthenticatorInterface $userAuthenticator;
private FormLoginAuthenticator $formLoginAuthenticator;
/**
* @param UserCheckerInterface $checker
* @param UserAuthenticatorInterface $userAuthenticator
* @param FormLoginAuthenticator $formLoginAuthenticator
*/
public function __construct(UserCheckerInterface $checker, UserAuthenticatorInterface $userAuthenticator, FormLoginAuthenticator $formLoginAuthenticator)
{
$this->checker = $checker;
$this->userAuthenticator = $userAuthenticator;
$this->formLoginAuthenticator = $formLoginAuthenticator;
}
public function login(User $user, Request $request): void
{
$this->checker->checkPreAuth($user);
$this->userAuthenticator->authenticateUser($user, $this->formLoginAuthenticator, $request);
}
}
接受的版本不适用于 symfony 3.3。用户将在下一个请求而不是当前请求中进行身份验证。原因是 ContextListener 检查先前的会话是否存在,如果不存在,它将清除安全 TokenStorage。解决这个问题的唯一方法(非常黑客)是通过在当前请求上手动初始化会话(和 cookie)来伪造先前会话的存在。
如果您找到更好的解决方案,请告诉我。
顺便说一句,我不确定这是否应该与已接受的解决方案合并。
private function logUserIn(User $user)
{
$token = new UsernamePasswordToken($user, null, "common", $user->getRoles());
$request = $this->requestStack->getMasterRequest();
if (!$request->hasPreviousSession()) {
$request->setSession($this->session);
$request->getSession()->start();
$request->cookies->set($request->getSession()->getName(), $request->getSession()->getId());
}
$this->tokenStorage->setToken($token);
$this->session->set('_security_common', serialize($token));
$event = new InteractiveLoginEvent($this->requestStack->getMasterRequest(), $token);
$this->eventDispatcher->dispatch("security.interactive_login", $event);
}
上面的代码假设您的防火墙名称(或共享上下文名称)是
common
。
试试这个:对于 Symfony 3 用户,不要忘记进行此更正以测试密码的相等性(因为此链接上显示的测试密码的方法不起作用):
$current_password = $user->getPassword();
$user_entry_password = '_got_from_a_form';
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user_entry_password, $user->getSalt());
if(hash_equals($current_password, $password)){
//Continue there
}
// I hash the equality process for more security
对于 Symfony 5,您可以使用开箱即用的功能来创建登录和注册表单。
使用 Symfony\Component\Security\Guard\GuardAuthenticatorHandler 是关键点。
注册成功后即可在注册控制器中使用GuardAuthenticatorHandler。它登录用户并从 LoginFormAuthenticator 重定向到 onAuthenticationSuccess 中定义的页面。
下面,我添加了一些代码片段。
<?php
namespace App\Controller\Login;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* @Route("/logout", name="app_logout")
*/
public function logout()
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
<?php
namespace App\Security;
use App\Entity\User\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
// if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
// return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
// }
//
// // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
// throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
<?php
namespace App\Controller;
use App\Entity\User\User;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
class RegistrationController extends AbstractController
{
private EntityManagerInterface $objectManager;
private UserPasswordEncoderInterface $passwordEncoder;
private GuardAuthenticatorHandler $guardHandler;
private LoginFormAuthenticator $authenticator;
/**
* RegistrationController constructor.
* @param EntityManagerInterface $objectManager
* @param UserPasswordEncoderInterface $passwordEncoder
* @param GuardAuthenticatorHandler $guardHandler
* @param LoginFormAuthenticator $authenticator
*/
public function __construct(
EntityManagerInterface $objectManager,
UserPasswordEncoderInterface $passwordEncoder,
GuardAuthenticatorHandler $guardHandler,
LoginFormAuthenticator $authenticator
) {
$this->objectManager = $objectManager;
$this->passwordEncoder = $passwordEncoder;
$this->guardHandler = $guardHandler;
$this->authenticator = $authenticator;
}
/**
* @Route("/registration")
*/
public function displayRegistrationPage()
{
return $this->render(
'registration/registration.html.twig',
);
}
/**
* @Route("/register", name="app_register")
*
* @param Request $request
* @return Response
*/
public function register(Request $request)
{
// if (!$this->isCsrfTokenValid('sth-special', $request->request->get('token'))) {
// return $this->render(
// 'registration/registration.html.twig',
// ['errorMessage' => 'Token is invalid']
// );
// }
$user = new User();
$user->setEmail($request->request->get('email'));
$user->setPassword(
$this->passwordEncoder->encodePassword(
$user,
$request->request->get('password')
)
);
$user->setRoles(['ROLE_USER']);
$this->objectManager->persist($user);
$this->objectManager->flush();
return $this->guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$this->authenticator,
'main' // firewall name in security.yaml
);
return $this->render('base.html.twig');
}
}
经过几天的调试和调查,我终于在 Symfony 4.4 上以编程方式对用户进行了身份验证。我想这种方法也应该适用于较新的版本。
重要的是要获取防火墙的正确名称,
main
在我的例子中,在您的security.yml
security:
firewalls:
main:
pattern: ^/
#...
然后将其传递到会话中:
$session->set('_security_main', serialize($token));
登录操作的完整代码:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
//...
public function loginAction(
Request $request,
TokenStorageInterface $tokenStorage,
SessionAuthenticationStrategyInterface $sessionStrategy,
AuthenticationProviderManager $authManager
) {
// ...
if ($request->getMethod() == "POST") {
// Fetching user and checking password logic...
$em->flush();
// Create an authenticated token for the User.
// Here, "main" is the name of the firewall in your security.yml
$token = new UsernamePasswordToken(
$email,
$password,
'main', // firewall name in security.yaml
$user->getRoles()
);
$session = $request->getSession();
if (!$request->hasPreviousSession()) {
$request->setSession($session);
$request->getSession()->start();
$request->cookies->set($request->getSession()->getName(), $request->getSession()->getId());
}
$session->set(Security::LAST_USERNAME, $email);
// Authenticate user
$authManager->authenticate($token);
$sessionStrategy->onAuthentication($request, $token);
// For older versions of Symfony, use "security.context" here
$tokenStorage->setToken($token);
$session->set('_security_main', serialize($token));
$session->remove(Security::AUTHENTICATION_ERROR);
$session->remove(Security::LAST_USERNAME);
// Fire the login event
$event = new InteractiveLoginEvent($request, $token);
$this->get('event_dispatcher')->dispatch($event, SecurityEvents::INTERACTIVE_LOGIN);
// return success response here
}
}
$this->get('fos_user.security.login_manager')->logInUser('main', $user);
其中
'main'
是您在 security.yml
中的防火墙名称,$user
是代表您要登录的用户的对象。
这适用于我的 Symfony 2.8 项目,您可以通过运行
php app/console debug:container
来检查您版本中的 login_manager 服务。
自 Symfony 6.2 起,有更简单的方法以编程方式对用户进行身份验证。以下示例来自 来自 Symfony 博客:
use Symfony\Bundle\SecurityBundle\Security;
// ...
class SomeService
{
public function __construct(
private Security $security,
) {
}
public function someMethod()
{
// fetch a UserInterface object somehow (e.g. from a database)
$user = ...
// login the user programmatically
$this->security->login($user);
// if you have many authenticators associated to the current firewall,
// you must pass explicitly the name of authenticator to use
$this->security->login($user, 'form_login');
$this->security->login($user, SomeApiKeyAuthenticator::class);
// ...
}
}