Symfony 4.3 PHP:如何从 302 重定向响应中读取 cookie?

问题描述 投票:0回答:2

我想做什么(在控制器中)

  1. 收到请求,基于该请求
  2. 发送外部 HTTP 请求
  3. 根据其解析响应
  4. 回复回复

我计划有多个功能都遵循这种模式。他们被一一叫,连续的。

Symfony 4.3 引入了 HttpClient 组件和 BrowserKit 组件,乍一看似乎符合要求。但事实证明,事情变得更加复杂。外部 HTTP 请求被发送到使用 cookie 跟踪会话的服务器,这应该不是什么太不寻常的事情。如果发送的请求没有 cookie,服务器始终会使用相同的登陆页面内容进行响应。所以我们需要cookie。不用担心,对初始请求的响应带有一个“Set-Cookie”标头,其中包含后续请求中“Cookie”标头中发送的 cookie。带有 cookie 的响应的 HTTP 状态是“302 Found”,在正常的 Web 环境中,使用 Web 浏览器,重定向到请求中具有相同 cookie 的下一页。在我的 Symfony 控制器中复制这个动作似乎非常棘手。扩展我现在想做的步骤 2:

2.a.发送外部 HTTP 请求

2.b.从 302 响应中读取 cookie(不自动重定向)

2.c.设置后续请求的“Cookie”头,值来自2.b.

2.d.将请求发送到新目标(在 302 响应的“Location”标头中设置)

这是控制器的一些代码:

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpFoundation\JsonResponse;

public function init(Request $request): JsonResponse
{
    $url = "external url";
    $client = HttpClient::create();
    try {
        $response = $client->request('GET', $url, [
            'max_redirects' => -1
        ]);
    } catch (RedirectionException $redirectionException) {

    }
    return new JsonResponse([], 200);
}

不设置 max_redirects 意味着客户端遵循重定向并最终返回不带任何 cookie 的 200 响应。将值设置为 -1 将引发重定向 再次出现异常,带有 cookie 的 302 响应似乎丢失了。

我还尝试了 HttpBrowser 组件:

$cookieJar = new CookieJar();
$client = new HttpBrowser(HttpClient::create(), null, $cookieJar);
$client->request('GET', $url);

但是我在没有 cookie 的情况下收到 200 响应,因为它会自动遵循重定向。

我知道这里提到的组件是 Symfony 4.3 的新组件并且是“实验性的”,但我也不认为我的任务过于复杂或不寻常。但也许 Symfony 还有其他 http 客户端更适合这项任务?

php http symfony4
2个回答
2
投票

事实证明,通过尝试 Symfony 的其他 HTTP 客户端组件而不是本机组件,这个任务更容易完成。所以我暂时把 Symfony HttpClient 和 HttpBrowser 放在一边。我设法用 guzzlehttp/guzzle 版本 6.3.3 实现了这个问题的解决方案。这是我的控制器功能中的样子:

use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

public function init(Request $request): JsonResponse {
    $client = new Client([
        'base_uri' => 'an external domain',
        'cookies' => true
    ]);
    // get the 302 response, without following
    $response = $client->get('path',[
        'allow_redirects' => false
    ]);
    // get the cookie from the 302 response
    $jar = new \GuzzleHttp\Cookie\CookieJar(true,[$response->getHeader('Set-Cookie')]);
    // follow the redirect manually, with the cookie from the 302 response
    $redirectResponse = $client->request('GET', $response->getHeader('Location')[0], [
        'cookies' => $jar
    ]);

    $jsonResponse = new JsonResponse(['message' => 'my message'],200);
    $originalCookie = Cookie::fromString($response->getHeader('Set-Cookie')[0]);
    $newCookie = Cookie::create(
        $originalCookie->getName(),
        $originalCookie->getValue(),
        $originalCookie->getExpiresTime(),
        '/',
        'my domain',
        false,
        true,
        $originalCookie->isRaw(),
        $originalCookie->getSameSite()
    );

    $jsonResponse->headers->setCookie($newCookie);
    return $jsonResponse;
}

无法从响应中获取 cookie,或者由于域或路径不匹配而永远不会发回 cookie。因此,我创建了一个与原始 cookie 非常相似的新 cookie,然后将其发送回浏览器。这样后续请求中cookie就会回来,数据内容就可以向前发送了。

这里出现的其他问题是必须在外部服务器上处理的同源要求。

我仍然对如何使用原生 Symfony 4.3 组件完成所有这一切感兴趣,或者如果有人可以确认这是不可能的。


0
投票

这是我在 Symfony v6 上的做法


class UserInfoTest extends \Symfony\Bundle\FrameworkBundle\Test\WebTestCase
{
    private \Symfony\Bundle\FrameworkBundle\KernelBrowser $client;

    public function testGetUserInfo(): void
    {
        $this->client = static::createClient();
        // with this request user is redirect to another location with JWT in the cookie
        $this->client->request('GET', '/mock/login', [
            'allow_redirects' => false,
        ]);
        self::assertResponseStatusCodeSame(302);
        self::assertBrowserHasCookie('BEARER');

        //if you want to access the cookie:
        // $cookieJar = $this->client->getCookieJar();
        // $cookieJar->get('BEARER')->getValue();

        // here JWT cookies is in browser/client cookie, when making a request JWT cookie is attached to the request.
        $this->client->jsonRequest('GET', '/api/user-info');
        self::assertResponseIsSuccessful();
        self::assertResponseStatusCodeSame(200);

        $response = $this->client->getResponse();
        $content = \json_decode($response->getContent(), true);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.