本文介绍了Symfony2 phpunit功能测试自定义用户身份验证在重定向后失败(与会话相关)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介:能够使用的自定义用户实施和Wordpress用户:

在我们的项目中,我们实现了一个自定义用户提供程序(针对Wordpress用户的-实现UserProviderInterface )和相应的自定义用户( WordpressUser实现了UserInterface,EquatableInterface ).我已经在security.yml中设置了防火墙,并实施了几个投票者.

In our project, we have implemented a custom user provider (for Wordpress users - implements UserProviderInterface) with corresponding custom user (WordpressUser implements UserInterface, EquatableInterface). I have setup a firewall in the security.yml and implemented several voters.

# app/config/security.yml
security:
    providers:
        wordpress:
            id: my_wordpress_user_provider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        default:
            anonymous: ~
            http_basic: ~
            form_login:
                login_path: /account

功能性phpunit测试:

到目前为止还不错-但现在最棘手的部分是:在功能性phpunit测试中模拟经过身份验证的(Wordpress)用户.我已经成功模拟了WordpressUserProvider,因此模拟的WordpressUser将在loadUserByUsername(..)上返回.在我们的BaseTestCase中(扩展了WebTestCase),模拟的WordpressUser得到身份验证,并且令牌存储到会话中.

So far so good - but now the tricky part: mocking authenticated (Wordpress) users in functional phpunit tests. I have succeeded mocking the WordpressUserProvider so a mocked WordpressUser will be returned on loadUserByUsername(..). In our BaseTestCase (extends WebTestCase) the mocked WordpressUser gets authenticated and the token is stored to session.

//in: class BaseTestCase extends WebTestCase

/**
 * Login Wordpress user
 * @param WordpressUser $wpUser
 */
private function _logIn(WordpressUser $wpUser)
{
    $session = self::get('session');

    $firewall = 'default';
    $token = new UsernamePasswordToken($wpUser, $wpUser->getPassword(), $firewall, $wpUser->getRoles());
    $session->set('_security_' . $firewall, serialize($token));
    $session->save();

    $cookie = new Cookie($session->getName(), $session->getId());
    self::$_client->getCookieJar()->set($cookie);
}

问题:在新请求上丢失会话数据:

简单测试在身份验证部分成功.直到使用重定向进行测试.用户仅通过一个请求的身份验证,并在重定向后被忘记".这是因为Symfony2测试客户端将在每次请求时关闭内核并引导内核,这样,会话会丢失.

The simple tests succeed on the authentication part. Until tests with a redirect. The user is only authenticated one request, and 'forgotten' after a redirect. This is because the Symfony2 test client will shutdown() and boot() the kernel on each request, and in this way, the session gets lost.

解决方法/解决方案:

问题12680675 中提供的解决方案中,仅用户ID用于UsernamePasswordToken(..)来解决此问题.我们的项目需要完整的用户对象.

In a solution provided in question 12680675 only user ID should be used for the UsernamePasswordToken(..) to solve this. Our project needs the full user object.

中提供的解决方案中,无法在功能测试使用了基本的HTTP身份验证.在这种情况下,不能使用完整的用户对象(包括角色).

In the solution provided in Unable to simulate HTTP authentication in functional test the basic HTTP authentication is used. In this case the full user object - including roles - cannot be used.

根据在Symfony2中隔离测试,您可以坚持通过覆盖测试客户端中的doRequest()方法来实例化实例.按照建议,我创建了一个自定义测试客户端,并在doRequest()方法上进行了覆盖.

As suggested by Isolation of tests in Symfony2 you can persist instances by overriding the doRequest() method in the test client. As suggested I have created a custom test client and made an override on the doRequest() method.

自定义测试客户端,以存储"请求之间的会话数据:

namespace NS\MyBundle\Tests;

use Symfony\Bundle\FrameworkBundle\Client as BaseClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Class Client
 * Overrides and extends the default Test Client
 * @package NS\MyBundle\Tests
 */
class Client extends BaseClient
{
    static protected $session;

    protected $requested = false;

    /**
     * {@inheritdoc}
     *
     * @param Request $request A Request instance
     *
     * @return Response A Response instance
     */
    protected function doRequest($request)
    {
        if ($this->requested) {
            $this->kernel->shutdown();
            $this->kernel->boot();
        }

        $this->injectSession();
        $this->requested = true;

        return $this->kernel->handle($request);
    }

    /**
     * Inject existing session for request
     */
    protected function injectSession()
    {
        if (null === self::$session) {
            self::$session = $this->getContainer()->get('session');
        } else {
            $this->getContainer()->set('session', self::$session);
        }
    }
}

如果没有if语句包含shutdown()和boot()调用,则此方法或多或少地起作用.在某些奇怪的问题中,找不到$ _SERVER索引键,因此我想为系统的其他方面重新实例化内核容器.在保留if语句的同时,尽管在请求之前和期间/之后会话数据是相同的,但用户无法通过身份验证(由var_export检查以记录日志).

Without the if statement holding the shutdown() and boot() calls, this method is working more or less. There are some weird problems where $_SERVER index keys cannot be found so I would like to properly re-instantiate the kernel container for other aspects of the system. While keeping the if statement, users cannot be authenticated, though the session data is the same before and during/after the request (checked by var_export to log).

问题:

在这种导致身份验证失败的方法中,我缺少什么?验证(和会话检查)是在内核boot()上/之后直接完成的,还是我遗漏了其他东西?有没有人拥有另一个/更好的解决方案来保持会话完整,以便在功能测试中对用户进行身份验证?预先感谢您的回答.

What am I missing in this this approach that causes the authentication to fail? Is the authentication (and session check) done directly on/after kernel boot() or am I missing something else? Does anyone has another/better solution to keep the session intact so users will be authenticated in functional tests? Thank you in advance for your answer.

-编辑-

--EDIT--

此外:测试环境的会话存储设置为session.storage.mock_file.这样,会话应该已经在请求之间持久保存,如Symfony2组件所述此处. (第二个)请求后在测试中检查时,该会话似乎是完整的(但是以某种方式被身份验证层忽略了?).

In addition: the session storage for the test environment is set to session.storage.mock_file. In this way, the session should already be persisted between requests as describe by Symfony2 components here. When checked in the test after a (second) request, the session seems to be intact (but somehow ignored by the authentication layer?).

# app/config/config_test.yml
# ..
framework:
    test: ~
    session:
        storage_id: session.storage.mock_file
    profiler:
        collect: false

web_profiler:
    toolbar: false
    intercept_redirects: false
# ..

推荐答案

我的假设很接近.不是会话没有被持久化,问题在于,在新请求下内核已擦除"了模拟服务.这是功能性phpunit测试的基本行为.

My assumptions were close; it was not the session that was not persisted, the problem was in the case that mocked services are 'erased' by the kernel at a fresh request. This is the basic behaviour of functional phpunit testing...

我发现在Symfony \ Component \ Security \ Http \ Firewall \ AccessListener中进行调试时,这一定是问题所在.在那里找到了令牌,并且(不再是)模拟的自定义WordpressUser在那里-空.这解释了为什么在上述建议的解决方法中,仅设置用户名而不是设置用户对象的原因(不需要模拟的User类).

I found out that this had to be the problem while debugging in the Symfony\Component\Security\Http\Firewall\AccessListener. There the token was found, and the (not anymore) mocked custom WordpressUser was there - empty. This explains why setting the username only instead of user object worked in the suggested workarounds stated above (no need of the mocked User class).

解决方案

首先,您不需要按照上面我的问题中的建议覆盖客户端.为了能够持久保存您的模拟类,您将必须扩展AppKernel并使用闭包作为参数进行某种内核修改器覆盖.在此处有一个解释在LyRiXx博客上.注入闭包后,您可以在请求后恢复服务模拟.

First of all, you don't need to override the Client as suggested in my question above. To be able to persist your mocked classes, you will have to extend the AppKernel and make some sort of kernel-modifier override with a closure as parameter. There is an explanation here on LyRiXx Blog. After injecting with a closure, you could restore the service mock after a request.

// /app/AppTestKernel.php

/**
 * Extend the kernel so a service mock can be restored into the container
 * after a request.
 */

require_once __DIR__.'/AppKernel.php';

class AppTestKernel extends AppKernel
{
    private $kernelModifier = null;

    public function boot()
    {
        parent::boot();

        if ($kernelModifier = $this->kernelModifier) {
            $kernelModifier($this);
        };
    }

    /**
     * Inject with closure
     * Next request will restore the injected services
     *
     * @param callable $kernelModifier
     */
    public function setKernelModifier(\Closure $kernelModifier)
    {
        $this->kernelModifier = $kernelModifier;
    }
}

用法(在功能测试中):

$mock = $this->getMockBuilder(..);
..
static::$kernel->setKernelModifier(function($kernel) use ($mock) {
    $kernel->getContainer()->set('bundle_service_name', $mock);
});

我仍然必须调整类并扩展WebTestCase类,但这似乎对我有用.我希望我可以用这个答案将其他人指向正确的方向.

I still have to tweak the class and extended WebTestCase class, but this seems to work for me. I hope I can point someone else in the right(?) direction with this answer.

这篇关于Symfony2 phpunit功能测试自定义用户身份验证在重定向后失败(与会话相关)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-31 21:49