[英]Symfony Test - null token with Guard Authentication
我的 Symfony Web 应用程序中有一个 Guard 身份验证。 我想执行一些单元测试。 我无法在我的测试中模拟身份验证。 调用$tokenStorage->getToken()
时令牌保持为null
。
笔记:
dev
和prod
环境下工作。 重现:您可以从此repo (symfony 项目)重现错误。 这个 repo 定义了一个实体User
和一个自定义约束验证器ExampleValidator
。 在这个约束中,我需要有当前登录的用户。
代码示例:
手动创建用户后,测试中使用的login
功能:
private function logIn($firewallName = 'main'){
// dummy call to bypass the hasPreviousSession check
$crawler = $this->client->request('GET', '/');
$session = $this->client->getContainer()->get('session');
/** @var User $user */
$user = $this->entityManager->getRepository(User::class)
->findOneBy(['email' => 'user1@example.com']);
// you may need to use a different token class depending on your application.
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
$token = new PostAuthenticationGuardToken($user, $firewallName, [new Role('ROLE_CLIENT')]);
self::$kernel->getContainer()->get('security.token_storage')->setToken($token);
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
来自tokenStorage
的用户调用(来自服务功能):
class ExampleValidator extends ConstraintValidator{
protected $requestStack;
protected $em;
protected $user_id;
public function __construct(RequestStack $request,
EntityManager $em,
TokenStorage $tokenStorage){
$this->requestStack = $request;
$this->em = $em;
/** @var User $user */
// Token is always null
$user = $tokenStorage->getToken()->getUser();
$this->user_id = $user != "anon." ? $user->getId() : null;
}
/**
* @param $value
* @param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
// validation rules ...
}
}
LoginFormAuthenticator.php
<?php
namespace AppBundle\Security;
use AppBundle\Entity\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\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
private $loginAttemptRepository;
public function __construct(EntityManagerInterface $entityManager,
UrlGeneratorInterface $urlGenerator,
CsrfTokenManagerInterface $csrfTokenManager,
UserPasswordEncoderInterface $passwordEncoder){
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
/**
* @param Request $request
* @return bool
*/
public function supports(Request $request){
return $request->getPathInfo() == '/login_check' &&
$request->isMethod('POST') &&
$request->request->get('_password') !== null;
}
/**
* @param Request $request
* @return array|mixed|void|null
*/
public function getCredentials(Request $request){
$isLoginSubmit = $request->getPathInfo() == '/login_check' &&
$request->isMethod('POST') &&
$request->request->get('_password') !== null;
$isCaptcha = $request->request->get('captcha_set');
if ($isCaptcha == 1 && $request->request->get('_password') !== null) {
$secret = ...;
if($_POST['g-recaptcha-response'] !== null){
// Paramètre renvoyé par le recaptcha
$response = $_POST['g-recaptcha-response'];
$remoteip = $_SERVER['REMOTE_ADDR'];
$api_url = "https://www.google.com/recaptcha/api/siteverify?secret="
. $secret
. "&response=" . $response
. "&remoteip=" . $remoteip ;
$decode = json_decode(file_get_contents($api_url), true);
if ($decode['success'] == true) {
$username = $request->request->get('_username');
$password = $request->request->get('_password');
$csrfToken = $request->request->get('_csrf_token');
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
$request->getSession()->set(
Security::LAST_USERNAME,
$username
);
return [
'username' => $username,
'password' => $password,
];
}
else{
throw new CustomUserMessageAuthenticationException('Captcha invalids.');
}
}
else{
throw new CustomUserMessageAuthenticationException('Captcha invalids.');
}
}
else {
if (!$isLoginSubmit) {
// skip authentication
return;
}
$username = $request->request->get('_username');
$password = $request->request->get('_password');
$csrfToken = $request->request->get('_csrf_token');
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
$request->getSession()->set(
Security::LAST_USERNAME,
$username
);
return [
'username' => $username,
'password' => $password,
];
}
}
/**
* @param mixed $credentials
* @param UserProviderInterface $userProvider
* @return User|object|UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider){
$username = $credentials["username"];
$user = $this->entityManager->getRepository(User::class)
->findOneBy(['username' => $username]);
return $user;
}
/**
* @param mixed $credentials
* @param UserInterface $user
* @return bool
*/
public function checkCredentials($credentials, UserInterface $user){
$password = $credentials["password"];
$rep = false;
if ($this->passwordEncoder->isPasswordValid($user, $password)){
$rep = true;
}
return $rep;
}
/**
* @param Request $request
* @param TokenInterface $token
* @param string $providerKey
* @return RedirectResponse
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey){
$targetPath = null;
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('map'));
}
/**
* @return string
*/
protected function getLoginUrl(){
return $this->urlGenerator->generate('fos_user_security_login');
}
}
我相信您的问题的根源在于您正在使用多个容器实例。 特别是,您的logIn()
函数适用于客户端的容器,但验证器来自您在setUp()
期间启动的不同容器。 因此,您在logIn()
中对客户端容器所做的更改不会影响您实际测试的验证器。
在任何地方使用相同的容器,例如来自客户端的容器,应该可以解决这个问题。 对存储库的以下更改使测试通过:
diff --git a/tests/AppBundle/Validator/UserTest.php b/tests/AppBundle/Validator/UserTest.php
index f15c854..603e566 100644
--- a/tests/AppBundle/Validator/UserTest.php
+++ b/tests/AppBundle/Validator/UserTest.php
@@ -44,10 +44,7 @@ class UserTest extends WebTestCase{
$this->container = $this->client->getContainer();
$this->entityManager = $this->container->get('doctrine.orm.entity_manager');
- // Set validator
- $kernel = $this->createKernel();
- $kernel->boot();
- $this->validator = $kernel->getContainer()->get('validator');
+ $this->validator = $this->client->getContainer()->get('validator');
// Create one user
$this->createOneUser();
@@ -100,7 +97,7 @@ class UserTest extends WebTestCase{
// you may need to use a different token class depending on your application.
// for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
$token = new PostAuthenticationGuardToken($user, $firewallName, [new Role('ROLE_CLIENT')]);
- self::$kernel->getContainer()->get('security.token_storage')->setToken($token);
+ $this->client->getContainer()->get('security.token_storage')->setToken($token);
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.