我想做什么(在控制器中)
我计划有多个功能都遵循这种模式。他们被一一叫,连续的。
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 客户端更适合这项任务?
事实证明,通过尝试 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 组件完成所有这一切感兴趣,或者如果有人可以确认这是不可能的。
这是我在 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);
}
}