繁体   English   中英

Symfony 测试 - 带有 Guard 身份验证的空令牌

[英]Symfony Test - null token with Guard Authentication

我的 Symfony Web 应用程序中有一个 Guard 身份验证。 我想执行一些单元测试。 我无法在我的测试中模拟身份验证。 调用$tokenStorage->getToken()时令牌保持为null

笔记:

  • 登录身份验证在devprod环境下工作。
  • 我看到了很多相关主题,但没有成功和doc
  • Symfony 版本:3.4。

重现:您可以从此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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM