簡體   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