【问题标题】:Symfony3 sub request lose of sessionSymfony3 子请求会话丢失
【发布时间】:2018-01-04 15:30:35
【问题描述】:

我在使用 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]

如果我从 admin 路由向匹配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 键不见了,我的会话也不见了。

如果我不将原始请求的cookies复制到子请求中,会话不会丢失,但是如果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 请求,因为我希望能够获得 entities(实际对象)作为 Api 调用的结果。

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

任何想法将不胜感激。

感谢您的帮助。

【问题讨论】:

    标签: php api symfony session request


    【解决方案1】:

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

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

    // 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 键,因此完美地“可备份”为字符串。但是,如果在未来的情况下,会话包含在子请求中被修改的真实对象,这将根本不起作用。

    如果会话包含大量数据,那么性能如何?嗯

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-28
      • 1970-01-01
      • 2015-08-20
      • 2011-07-12
      • 2018-04-15
      • 2014-11-06
      • 2018-02-09
      • 1970-01-01
      相关资源
      最近更新 更多