Получение «SignatureDoesNotMatch» при использовании GuzzleHttp для перенаправления на предварительно подписанный URL-адрес S3

#php #amazon-s3 #redirect #guzzle

Вопрос:

Я использую API, который получает запрос POST с некоторыми параметрами и использует их для создания файла в S3. Этот API возвращает ответ на перенаправление 303 с Location: указанием подписанного URL-адреса для доступа к файлу S3.

Этот файл работает при доступе к API, например, через Postman, однако при доступе к нему через GuzzleHttp (v7.4) он завершается ошибкой SignatureDoesNotMatch .

При отладке я использовал код:

 $client = new Client([  'allow_redirects' =gt; true,  'on_stats' =gt; function (TransferStats $stats) {  var_dump($stats-gt;getEffectiveUri());  } ]);  $client-gt;post('https://api.example.com', [  'json' =gt; [ ... ] ]);  

Делая это, я подтвердил, что URL-адрес правильный, и копирование/вставка точного URL-адреса, к которому осуществляется доступ, действительно работает. Однако при использовании GuzzleHttp это вообще не работает.

Обновление: Разработчик API сообщил мне, что эта проблема возникла также из-за того, что они использовали версию 2 подписи AWS S3. Теперь они изменили его на v4, что заставляет мой код работать как есть (я думаю, что у них могли быть те же проблемы с другими клиентами).

Ответ №1:

Проблема оказалась в том, что, когда Guzzle следует перенаправлениям, он сохраняет большую часть исходных заголовков запросов. Однако вычисленная подпись Amazon также проверяет подпись на соответствие (по крайней мере, некоторым) заголовкам. Оскорбительным заголовком был Content-Type заголовок, который все еще отправлялся, хотя запрос больше не содержал никакого содержимого.

Чтобы исправить это, я создал новое промежуточное программное обеспечение Guzzle:

 use PsrHttpMessageRequestInterface;  class RemoveContentType {   public function __invoke(callable $handler) {  return function (RequestInterface $request, array $options) use ($handler) {  if ($request-gt;getHeaderLine('Content-Type')  amp;amp; $request-gt;getBody()-gt;getSize() === 0) {  return $handler($request-gt;withoutHeader('Content-Type'), $options);  }   return $handler($request, $options);  };  } }  

это приведет к удалению типа содержимого из запроса с пустым телом.

Затем я изменил свой код, чтобы использовать это промежуточное программное обеспечение:

 $stack = HandlerStack::create(); $stack-gt;push(new RemoveContentType ()); $client = new Client([  'handler' =gt; $stack,  'allow_redirects' =gt; true, ]);  $client-gt;post('https://api.example.com', [  'json' =gt; [ ... ] ]);  

Это, наконец, решило мою проблему.