Symfony2 phpunit 功能测试自定义用户身份验证在重定向后失败(与会话相关) [英] Symfony2 phpunit functional test custom user authentication fails after redirect (session related)

查看:34
本文介绍了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,因此将在 loadUserByUsername(..) 上返回模拟的 WordpressUser.在我们的 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身份验证中提供的解决方案中功能测试 使用基本的 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 NSMyBundleTests;

use SymfonyBundleFrameworkBundleClient as BaseClient;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

/**
 * Class Client
 * Overrides and extends the default Test Client
 * @package NSMyBundleTests
 */
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).

问题:

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

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.

--编辑--

另外:测试环境的会话存储设置为 session.storage.mock_file.通过这种方式,会话应该已经在 Symfony2 组件 here 中描述的请求之间持久化.在(第二次)请求后检查测试时,会话似乎完好无损(但身份验证层以某种方式忽略了?).

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...

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

I found out that this had to be the problem while debugging in the SymfonyComponentSecurityHttpFirewallAccessListener. 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 功能测试自定义用户身份验证在重定向后失败(与会话相关)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆