Symfony3子请求丢失会话

时间:2017-07-28 14:07:22

标签: php api symfony session request

使用Symfony 3进行子请求时遇到奇怪的行为。

上下文

我认为解释为什么我会做出导致问题的原因会更好,这可能有助于找到解决方案。 但如果阅读太多,你可以跳过这部分。

我开发了一种Api Platform的克隆,不是一个分叉,而是一个灵感的重写。 我想添加的功能之一是能够从服务器端调用Api。

Api本身做了很多工作(例如关系很好),所以让它做的非常有用 即使对于经典的服务器端应用程序(没有ajax调用),持久化/过滤等工作。

我发现这样做的最简单方法是执行子请求。我当前的实现在使用时看起来像这样:

// To get a collection of articles (output as array)
$this->proxy->get(Article::class)->collection($page, $itemsPerPages, $filters, $ordering)->asArray();

// To get a single article (output as entities)
$this->proxy->get(Article::class)->item('a2Ck2')->asObject();

// To persist an article (output as entities)
$this->proxy->persist(Article::class)->item(['title' => 'Super article'])->asObject();

你明白了。

当您调用as*方法时,会执行请求,并且执行此操作的代码非常简单:

// Proxy.php
public function execute(Request $request, Response &$response = null)
{
    $response = $this->kernel->handle($request);

    [...]
    // Handle the response status code, the content etc. Not important here.
}

execute方法的参数中给出的请求是这样构建的(这里显示了GET构建器):

// GetRequestBuilder.php
protected function createRequest(): Request
{
    $currentRequest = $this->getCurrentRequest();
    $request = Request::create(
        $this->generateUri([...]),
        Request::METHOD_GET,
        [],
        $currentRequest->cookies->all()
    );
    return $request;
}

问题

如果我有多个防火墙,并且当我执行的子请求与不同防火墙中的路由匹配时,会出现问题。

对于以下security配置:

# app/config/security.yml
security:
    encoders:
        AppBundle\Entity\User:
            algorithm: bcrypt

    providers:
        main:
            id: app.main_user_provider
        doctrine:
            entity:
                class: AppBundle:User
                property: username

    firewalls:
        backend:
            pattern: ^/admin
            anonymous: ~
            logout:
                path:   /admin/logout
                target: /admin/login
                invalidate_session: true
            stateless: false
            guard:
                entry_point: app.backend.login_form_authenticator
                authenticators: [app.backend.login_form_authenticator]
        frontend:
            pattern: ^/
            anonymous: ~
            logout:
                path:   /logout
                target: /login
                invalidate_session: true
            stateless: false
            guard:
                entry_point: app.frontend.login_form_authenticator
                authenticators: [app.frontend.login_form_authenticator]

如果我从管理路由发出子请求到与frontend防火墙匹配的路由,我将失去管理员会话。

如果我在Api通话之前和之后转储会话,它会给我以下内容:

array (size=3)
  '_security.backend.target_path' => string 'http://localhost/admin/api/formation/categories' (length=51)
  '_csrf/form' => string 'wt9Js9b8deT00XanUgEq23qXFqY8uHrt_j5i6D9Btj8' (length=43)
  '_security_backend' => string 'C:67:"Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken":1786:{a:2:{i:0;s:7:"backend";i:1;s:174'... (length=1868)

在子请求之后:

array (size=2)
  '_security.backend.target_path' => string 'http://localhost/admin/api/formation/categories' (length=51)
  '_csrf/form' => string 'wt9Js9b8deT00XanUgEq23qXFqY8uHrt_j5i6D9Btj8' (length=43)

_security_backend密钥已消失,我的会话也已消失。

如果我不将原始请求的cookie复制到子请求,会话不会丢失,但如果Api路由受到保护,我会遇到问题:

// GetRequestBuilder.php
protected function createRequest(): Request
{
    $currentRequest = $this->getCurrentRequest();
    $request = Request::create(
        $this->generateUri([...]),
        Request::METHOD_GET,
        [],
        // Removing this line and replace it with an empty array solves the problem of loosing the session.
        // But if the target route is behind the "backend" firewall, the sub request will be redirected to the login page.
        $currentRequest->cookies->all()
    );
    return $request;
}

我的问题

1)您是否认为这种做出子请求的方法足够强大,或者您是否知道获得相同结果的更好方法?

旁注:我不做CURL或任何其他方式来执行真实 http请求,因为我希望能够获得实体(实际对象)是Api调用的结果。

2)如果子请求与具有不同防火墙的路由匹配,您是否知道一种防止原始会话丢失的方法? 正如我现在所理解的那样,我看到的唯一方法是检测(不知道如何......)目标路由是否在同一个防火墙上,如果是这样的话只复制cookie。 似乎很难做到很难稳定。它看起来很脏**。

任何想法都会非常感激。

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

我发布了自己的答案,因为我找到了一种似乎可以处理案件的丑陋方式,但我觉得非常脏,如果有人能想到一个干净的方式来处理我会很高兴这种情况。

所以解决方案是在请求之前“备份”会话数据并在之后恢复...我这样做了:

// Proxy.php
public function execute(Request $request, Response &$response = null)
{
    try {
        $this->saveSession();
        $response = $this->kernel->handle($request);

        [...]
        // Handle the response status code, the content etc. Not important here.
    } finally {
        $this->restoreSession();
    }
}

/**
 * Saves the current session.
 */
private function saveSession()
{
    $this->sessionStack[] = (array)$this->session->all();
}

/**
 * Restores the latest session saved using saveSession().
 */
private function restoreSession()
{
    $data = array_pop($this->sessionStack);
    $this->session->clear();
    foreach ($data as $k => $v) {
        $this->session->set($k, $v);
    }
}

当然,我仍然会在子请求中复制当前请求的cookie。

这是非常基本的,但我似乎工作(至少我目前的情况)。 我不知道像这样复制会话数据的副作用,但我猜这是件坏事。

它只能起作用,因为我需要保留_security_backend键,这是一个序列化对象,因此完全可以“备份”为字符串。但是,如果在将来的情况下会话中包含正在子请求中修改的实际对象,则根本不起作用。

如果会话拥有大量数据,性能如何呢? HMM的

所以,如果你们中的任何一个人有更好的解决方案,我会全力以赴:)