Получение 403 (Запрещено) при загрузке на S3 с подписанным URL-адресом
Я пытаюсь создать предварительно подписанный URL-адрес, а затем загружать файл на S3 через браузер. Мой серверный код выглядит так, и он генерирует URL-адрес:
let s3 = new aws.S3({
// for dev purposes
accessKeyId: 'MY-ACCESS-KEY-ID',
secretAccessKey: 'MY-SECRET-ACCESS-KEY'
});
let params = {
Bucket: 'reqlist-user-storage',
Key: req.body.fileName,
Expires: 60,
ContentType: req.body.fileType,
ACL: 'public-read'
};
s3.getSignedUrl('putObject', params, (err, url) => {
if (err) return console.log(err);
res.json({ url: url });
});
Кажется, эта часть работает нормально. Я могу видеть URL-адрес, если я его запишу, и он передает его в интерфейс. Затем, на переднем конце, я пытаюсь загрузить файл с аксиомами и подписанным URL:
.then(res => {
var options = { headers: { 'Content-Type': fileType } };
return axios.put(res.data.url, fileFromFileInput, options);
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
}
При этом я получаю ошибку 403 Forbidden. Если я следую ссылке, там есть XML с дополнительной информацией:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
...etc
Ответы
Ответ 1
Ваш запрос должен точно соответствовать подписке. Одна очевидная проблема заключается в том, что вы фактически не включаете консервированный ACL в запрос, даже если вы включили его в подпись. Перейдите к следующему:
var options = { headers: { 'Content-Type': fileType, 'x-amz-acl': 'public-read' } };
Ответ 2
Если бы та же проблема, вот как вам нужно ее решить,
- Извлеките часть имени файла подписанного URL. Сделайте распечатку, которая правильно извлекает часть имени файла с параметрами querystring. Это важно.
- Кодировать в URI Кодирование имени файла с параметрами строки запроса.
- Верните URL-адрес из вашей лямбда с зашифрованным именем файла вместе с другим путем или с вашего узла.
Теперь сообщение из аксиомов с этим URL-адресом будет работать.
EDIT1: Ваша подпись также будет недействительной, если вы передадите неправильный тип контента.
Убедитесь, что тип контента, который у вас есть, содержит заранее подписанный URL-адрес, такой же, как тот, который вы используете для ввода.
Надеюсь, поможет.
Ответ 3
1) Возможно, вам придется использовать сигнатуры S3V4 в зависимости от того, как данные передаются в AWS (chunk versus stream). Создайте клиент следующим образом:
var s3 = new AWS.S3({
signatureVersion: 'v4'
});
2) Не добавляйте новые заголовки или не изменяйте существующие заголовки. Запрос должен быть точно подписан.
3) Убедитесь, что полученный URL соответствует тому, что отправляется в AWS.
4) Сделайте тестовый запрос, удалив эти две строки перед подписанием (и удалите заголовки из PUT). Это поможет сузить проблему:
ContentType: req.body.fileType,
ACL: 'public-read'
Ответ 4
Если вы пытаетесь использовать ACL, убедитесь, что у вашей роли Lambda IAM есть s3:PutObjectAcl
для данного Bucket, а также что в вашем баке есть s3:PutObjectAcl
для загрузки Принципала (user/iam/account, который загружает).
Это то, что я исправил после двойной проверки всех моих заголовков и всего остального.
Вдохновленный этим ответом fooobar.com/questions/17874160/...
Ответ 5
Получение ошибки 403 Forbidden для предварительно подписанной загрузки s3 put также может произойти по нескольким причинам, которые не сразу очевидны:
Это может произойти, если вы сгенерировали предварительно подписанный URL-адрес пут с использованием типа контента подстановочного знака, такого как image/*
, поскольку подстановочные знаки не поддерживаются.
Это может произойти, если вы сгенерировали предварительно подписанный URL-адрес с типом содержимого не указан, но затем передали заголовок типа содержимого при загрузке из браузера. Если вы не указали тип контента при генерации URL-адреса, вы должны пропустить тип контента при загрузке. Имейте в виду, что если вы используете инструмент загрузки, например Uppy, он может автоматически прикреплять заголовок типа контента, даже если вы его не указали. В этом случае вам придется вручную установить пустой заголовок типа контента.
В любом случае, если вы хотите поддерживать загрузку файлов любого типа, лучше всего передать тип содержимого файла конечной точке API и использовать этот тип содержимого при создании предварительно подписанного URL-адреса, который вы возвращаете своему клиенту.
Например, создание предварительно подписанного URL из вашего API:
const AWS = require('aws-sdk')
const uuid = require('uuid/v4')
async function getSignedUrl(contentType) {
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY
})
const signedUrl = await s3.getSignedUrlPromise('putObject', {
Bucket: 'mybucket',
Key: 'uploads/${uuid()}',
ContentType: contentType
})
return signedUrl
}
А затем отправляем запрос на загрузку из браузера:
import Uppy from '@uppy/core'
import AwsS3 from '@uppy/aws-s3'
this.uppy = Uppy({
restrictions: {
allowedFileTypes: ['image/*'],
maxFileSize: 5242880, // 5 Megabytes
maxNumberOfFiles: 5
}
}).use(AwsS3, {
getUploadParameters(file) {
async function _getUploadParameters() {
let signedUrl = await getSignedUrl(file.type)
return {
method: 'PUT',
url: signedUrl
}
}
return _getUploadParameters()
}
})
Для получения дополнительной информации см. также следующие два сообщения о переполнении стека: как-генерировать-aws-s3-pre-подписанный-url-запрос-без-знания-типа-контента и S3.getSignedUrl для приема нескольких типов контента