#node.js #amazon-s3 #postman
Вопрос:
При вызове конечной точки поста под председательством, возвращенной из моего node.js сервер Я получаю то, что выглядит как правильный ответ:
Обратите внимание, что я устанавливаю переменные среды, чтобы упростить повторение вызова POST.
Однако при вызове URL-адреса с подписью я получаю ответ 403 с кодом ошибки SignatureDoesNotMatch. У меня возникли большие трудности с поиском ресурсов об использовании URL-адреса, подписанного постом. Итак, мои вопросы заключаются в следующем:
- Правильно ли я называю URL-адрес, указанный председателем (см. Ниже)?
- Как я могу отладить, чтобы понять, что здесь происходит (я пытался включить журналы ведра, но ничего не регистрируется)?
- На что я мог бы обратить внимание далее, чтобы решить эту проблему?
При выполнении публикации я понимаю, что вызов заключается в том, чтобы опубликовать возвращенный URL-адрес и использовать данные формы, включающие все «поля» в теле, при этом последний ключ помечается как файл с прикрепленным файлом для загрузки. Из приведенного выше ответа я поэтому использовал следующий вызов в postman: Заголовки:
Тело (я пробовал как с включенным типом содержимого, так и без него):
Однако, когда я делаю этот вызов, я получаю ответ 403 «Запрещено» и код ошибки SignatureDoesNotMatch:
Вот код для создания 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 по этим заголовкам ужасна. Вот что я выяснил.
- Результатом
createPresignedPost()
является объект с двумя ключами:url
иfields
.fields
Объект содержит все поля формы и соответствующие значения, которые вы должны использовать при отправке сообщения. Вы скопируете эти поля и только эти поля в свой запрос почтальона. - Есть несколько полей, которые 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()
, поэтому вам не нужно создавать их вручную. 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-
.- Параметры
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-форма также работает, поэтому я подозреваю, что в моем оригинале было еще одно ошибочное пространство или что-то подобное. Спасибо за вашу помощь, я приму ваш ответ.