Вызов URL-адреса под председательством POST s3 в postman возвращает значение SignatureDoesNotMatch

#node.js #amazon-s3 #postman

Вопрос:

При вызове конечной точки поста под председательством, возвращенной из моего node.js сервер Я получаю то, что выглядит как правильный ответ:

ПОЛУЧИТЬ URL-адрес председателя Обратите внимание, что я устанавливаю переменные среды, чтобы упростить повторение вызова POST.

Однако при вызове URL-адреса с подписью я получаю ответ 403 с кодом ошибки SignatureDoesNotMatch. У меня возникли большие трудности с поиском ресурсов об использовании URL-адреса, подписанного постом. Итак, мои вопросы заключаются в следующем:

  1. Правильно ли я называю URL-адрес, указанный председателем (см. Ниже)?
  2. Как я могу отладить, чтобы понять, что здесь происходит (я пытался включить журналы ведра, но ничего не регистрируется)?
  3. На что я мог бы обратить внимание далее, чтобы решить эту проблему?

При выполнении публикации я понимаю, что вызов заключается в том, чтобы опубликовать возвращенный URL-адрес и использовать данные формы, включающие все «поля» в теле, при этом последний ключ помечается как файл с прикрепленным файлом для загрузки. Из приведенного выше ответа я поэтому использовал следующий вызов в postman: Заголовки: ПУБЛИКУЙТЕ заголовки URL-адресов, подписанных председателем

Тело (я пробовал как с включенным типом содержимого, так и без него): Тело

Однако, когда я делаю этот вызов, я получаю ответ 403 «Запрещено» и код ошибки SignatureDoesNotMatch:
403 ответ

Вот код для создания URL-адреса с указанием председателя (с использованием «@aws-sdk/client-s3»: «^3.25.0», «@aws-sdk/s3-сообщение с указанием председателя»: «^3.25.0» в package.json):

 const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");

const s3 = new S3Client({
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  },
  signatureVersion: "v4",
  region: "eu-west-2",
});
async function getSignedUrl() {

  const params = {
    Bucket: "richbits-test",
    Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
    Conditions: [["eq", "$Content-Type", "image/jpeg"]],
  };

  console.log(params);

  const signedUrl = await createPresignedPost(s3, params);

  return signedUrl;
}

 

Я дважды проверил корзину и ключи, но был бы признателен за некоторые советы относительно того, как я мог бы двигаться дальше, или за любые полезные ресурсы, которые могут помочь мне лучше понять использование этих URL-адресов, пожалуйста.

Ответ №1:

Вау, документация AWS по этим заголовкам ужасна. Вот что я выяснил.

  1. Результатом createPresignedPost() является объект с двумя ключами: url и fields . fields Объект содержит все поля формы и соответствующие значения, которые вы должны использовать при отправке сообщения. Вы скопируете эти поля и только эти поля в свой запрос почтальона.
  2. Есть несколько полей, которые AWS SDK добавит автоматически, в том числе bucket , key , policy , и несколько полей, необходимых для создания подписи. С помощью JavaScript SDK это были X-Amz-Algorithm , X-Amz-Credential , X-Amz-Date , и X-Amz-Signature (хотя для Python SDK это были AWSAccessKeyId и signature ). Они будут возвращены в fields ключе от createPresignedPost() , поэтому вам не нужно создавать их вручную.
  3. Fields Параметр » из createPresignedPost() » предназначен для дополнительных полей, которые вы хотите добавить в форму. Здесь указан весь набор полей, которые можно было бы добавить. Однако SDK обрабатывает многие из них для вас, особенно необходимые. Согласно документам Python SDK, поля, которые вы могли бы включить в Fields acl ,, Cache-Control , Content-Type , Content-Disposition , Content-Encoding , Expires , success_action_redirect , redirect , success_action_status , и x-amz-meta- .
  4. Параметры Fields и Conditions createPresignedPost() должны быть синхронизированы таким образом, чтобы любое поле в Fields также имело условие в Conditions и наоборот.

В целом, я считаю, что проблема с вашей getSignedUrl() функцией заключалась в том, что в ней отсутствовал Fields параметр, для createPresignedPost() которого была пара ключ-значение Content-Type (поскольку у вас есть условие Content-Type ). Без параметра Content-Type in Fields вычисляемая подпись не включала Content-Type бы это поле. Если вы затем опустите Content-Type поле, ваша подпись может совпадать, но условия вашей политики не будут выполнены, поскольку она должна Content-Type присутствовать и соответствовать image/jpeg . Если бы вы включили Content-Type , то ваша подпись не совпадала бы.

Вот может что должно сработать для вас.

 const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");

const s3 = new S3Client({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  signatureVersion: "v4",
  region: "eu-west-2",
});
async function getSignedUrl() {

  const params = {
    Bucket: "richbits-test",
    Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
    Conditions: [["eq", "$Content-Type", "image/jpeg"]],
    Fields: {"Content-Type": "image/jpeg"},
  };

  console.log(params);

  const signedUrl = await createPresignedPost(s3, params);

  return signedUrl;
}
getSignedUrl().then(res => {
        console.log(res)
})
 

Результатом этого должно быть

 {
  Bucket: 'richbits-test',
  Key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
  Conditions: [ [ 'eq', '$Content-Type', 'image/jpeg' ] ],
  Fields: { 'Content-Type': 'image/jpeg' }
}
{
  url: 'https://s3.eu-west-2.amazonaws.com/richbits-test',
  fields: {
    'Content-Type': 'image/jpeg',
    bucket: 'richbits-test',
    'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
    'X-Amz-Credential': '<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request',
    'X-Amz-Date': '20211005T111446Z',
    key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
    Policy: '<SOME_BASE64_ENCODED_STRING>',
    'X-Amz-Signature': '<THE_SIGNATURE>'
  }
}
 

Тогда ваш запрос на публикацию будет включать поля формы Content-Type , bucket , X-Amz-Algorithm , X-Amz-Credential , X-Amz-Date , Policy , и X-Amz-Signature . Вот как будет выглядеть HTML-форма.

 <html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>

  <form action="https://s3.eu-west-2.amazonaws.com/richbits-test" method="post" enctype="multipart/form-data">
    Bucket:
    <input type="input" name="bucket" value="richbits-test" /><br />
    Key to upload: 
    <input type="input"  name="key" value="d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f" /><br />
    Content-Type: 
    <input type="input"  name="Content-Type" value="image/jpeg" /><br />
    <input type="text"   name="X-Amz-Credential" value="<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request" />
    <input type="text"   name="X-Amz-Date" value="20211005T111446Z" />

    <input type="hidden" name="Policy" value="<SOME_BASE64_ENCODED_STRING>" />'
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="hidden" name="X-Amz-Signature" value="<THE_SIGNATURE>" />
    
    File: 
    <input type="file"   name="file" /> <br />
    <!-- The elements after this will be ignored -->
    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>
  
</html>
 

Что касается отладки этой проблемы, я не нашел ничего полезного в POST запросе к S3. Я либо не получил ответа, либо получил несанкционированный статус, без каких-либо указаний на то, в чем заключалась проблема.

Комментарии:

1. Большое спасибо, я ценю, что вы нашли время ответить. Я попробовал предложенные вами изменения и все равно получил тот же результат. Я попробовал с почтальоном, а также попробовал с формой, которую вы предоставили. В первом случае я все еще получаю SignatureDoesNotMatch, а во втором ошибка отказа в доступе. Я могу сделать еще больше, чтобы понять, почему существует разница между двумя попытками, но пока я все еще не знаю, что может происходить.

2. @Richbits, когда вы получили ошибку SignatureDoesNotMatch, вы добавили Fields параметр для createPresignedPost() , а также включили Content-Type его в тело вашего запроса в Postman? Вы уверены, что ваши переменные среды почтальона работают правильно?

3. Да, я передаю это: const params = { Ведро: «richbits-тест», Ключ: «d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f», Условия: [[«эквалайзер», «$Тип содержимого», «изображение/jpg»]], Поля: { «Тип содержимого»: «изображение/jpg» }, }; и включенный тип содержимого в тело. Я также воспользовался страницей, которую вы опубликовали выше, создал нового пользователя и дважды проверил учетные данные. Я понятия не имею сейчас, я собираюсь попробовать с помощью put, который должен, по крайней мере, подтвердить, что я правильно использую creds.

4. @Richbits, я также попробовал запрос с помощью Postman, и он сработал, используя настройки, которые у вас есть на ваших изображениях. Я заметил, что в вашем запросе почтальона в поле «Тип содержимого» было установлено значение «изображение/jpeg», и в вашем комментарии говорилось, что ваш params createPresignedPost запрос включен Fields: {"Content-Type": "image/jpg"} . Я не уверен, была ли это опечатка, но эти два должны совпадать; они оба должны image/jpeg совпадать .

5. Да, я думаю, что пытался использовать image/jpg, так как это соответствует расширению, но с тех пор я просмотрел и знаю, что это недопустимый тип mime. Я просмотрел с нуля и обнаружил, что в Postman после переменной политики был пробел, так что, вероятно, это объясняет, почему он там не работал. HTML-форма также работает, поэтому я подозреваю, что в моем оригинале было еще одно ошибочное пространство или что-то подобное. Спасибо за вашу помощь, я приму ваш ответ.