php 如何以编程方式登录/验证用户?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9550079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to programmatically login/authenticate a user?
提问by rayfranco
I'd like to log the user in right after the registration process, without passing by the login form.
我想在注册过程后立即登录用户,而不是通过登录表单。
Is this possible ? I've found a solution with FOSUserBundle
, but I'm not using it on the project I'm actually working on.
这可能吗 ?我已经找到了一个解决方案FOSUserBundle
,但我没有在我实际从事的项目中使用它。
Here is my security.yml, I'm working with two firewalls. The plain text encoder is just for testing.
这是我的 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 }
回答by richsage
Yes, you can do this via something similar to the following:
是的,您可以通过类似于以下内容的方式执行此操作:
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
}
}
The event firing at the end isn't automatically done when you set a token into the context, whereas it would be normally when using eg a login form or similar. Hence the reason for including it here. You may need to adjust the type of token used, depending on your use case - the UsernamePasswordToken
shown above is a core token, but you can use others if required.
当您将令牌设置到上下文中时,最后触发的事件不会自动完成,而在使用例如登录表单或类似表单时通常会触发。因此,将其包含在此处的原因。您可能需要根据您的用例调整使用的令牌类型 -UsernamePasswordToken
上面显示的是核心令牌,但如果需要,您可以使用其他令牌。
Edit: Adjusted the above code to explain the 'public' parameter and also add in the roles of the user into the token creation, based on Franco's comment below.
编辑:根据下面弗朗哥的评论,调整了上面的代码以解释“公共”参数,并将用户的角色添加到令牌创建中。
回答by pinkeen
The accepted version will not work with symfony 3.3. User will be authenticated in the next request instead of the current one. The reason is that ContextListener checks for previous session existence and if not exists it will clear the security TokenStorage. The only way around this (hackish as hell) is to fake the existence of previous session by manually initialising the session (and cookie) on the current request.
接受的版本不适用于 symfony 3.3。用户将在下一个请求而不是当前请求中进行身份验证。原因是 ContextListener 检查以前的会话是否存在,如果不存在,它将清除安全 TokenStorage。解决这个问题的唯一方法(骇人听闻)是通过在当前请求上手动初始化会话(和 cookie)来伪造先前会话的存在。
Let me know if you find a better solution.
如果您找到更好的解决方案,请告诉我。
BTW I am not sure if this should be merged with the accepted solution.
顺便说一句,我不确定这是否应该与已接受的解决方案合并。
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);
}
The above code assumes that your firewall name (or shared context name) is common
.
上面的代码假设您的防火墙名称(或共享上下文名称)是common
.
回答by Somen Diégo
Try this : For Symfony 3 users, do not forget to make this correction to test the equality of the passwords (as the method shown to test the password on this link is not working) :
试试这个:对于 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
+ info : hash_equals_function
+ 信息:hash_equals_function
回答by mtwegrzycki
For Symfony 5, you can use out of the box functionalities to create login and registration forms.
对于 Symfony 5,您可以使用开箱即用的功能来创建登录和注册表单。
- How to create login form: https://symfony.com/doc/current/security/form_login_setup.html
- How to create registration form: https://symfony.com/doc/current/doctrine/registration_form.html
- 如何创建登录表单:https: //symfony.com/doc/current/security/form_login_setup.html
- 如何创建注册表:https: //symfony.com/doc/current/doctrine/registration_form.html
Using Symfony\Component\Security\Guard\GuardAuthenticatorHandler is key point.
使用 Symfony\Component\Security\Guard\GuardAuthenticatorHandler 是关键点。
You can use GuardAuthenticatorHandler in registration controller after successful registration. It logs in user and redirects to page defined in onAuthenticationSuccess from LoginFormAuthenticator.
注册成功后可以在注册控制器中使用GuardAuthenticatorHandler。它登录用户并从 LoginFormAuthenticator 重定向到 onAuthenticationSuccess 中定义的页面。
Below, I added some code snippets.
下面,我添加了一些代码片段。
<?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');
}
}