J'essaie de générer une URL pré-signée puis de télécharger un fichier sur S3 via un navigateur. Mon code côté serveur ressemble à ceci, et il génère l'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 });
});
Cette partie semble bien fonctionner. Je peux voir l'URL si je la connecte et qu'elle la transmet au front-end. Ensuite, sur le front-end, j'essaie de télécharger le fichier avec axios et l'URL signée:
.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);
});
}
Avec cela, j'obtiens l'erreur 403 interdite. Si je suis le lien, il y a du XML avec plus d'informations:
<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
La réception d'une erreur interdite 403 pour un téléchargement de vente s3 pré-signé peut également se produire pour deux raisons qui ne sont pas immédiatement évidentes:
Cela peut se produire si vous générez une URL de vente pré-signée en utilisant un type de contenu générique tel que image/*
, car les caractères génériques ne sont pas pris en charge.
Cela peut arriver si vous générez une URL de vente pré-signée avec aucun type de contenu spécifié , mais que vous passez ensuite un en-tête de type de contenu lors du téléchargement à partir du navigateur. Si vous ne spécifiez pas de type de contenu lors de la génération de l'URL, vous devez omettre le type de contenu lors du téléchargement. Sachez que si vous utilisez un outil de téléchargement comme ppy , il peut joindre automatiquement un en-tête de type de contenu même si vous n'en spécifiez pas. Dans ce cas, vous devez définir manuellement l'en-tête du type de contenu pour qu'il soit vide.
Dans tous les cas, si vous souhaitez prendre en charge le téléchargement de tout type de fichier, il est probablement préférable de transmettre le type de contenu du fichier à votre point de terminaison api et d'utiliser ce type de contenu lors de la génération de votre URL pré-signée que vous retournez à votre client.
Par exemple, générer une URL pré-signée à partir de votre 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
}
Et puis envoyer une demande de téléchargement depuis le navigateur:
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()
}
})
Pour plus d'informations, consultez également ces deux publications de débordement de pile: comment-générer-aws-s3-pré-signé-demande d'url-sans-connaître-type de conten et S3. getSignedUrl pour accepter plusieurs types de conten
1) Vous devrez peut-être utiliser des signatures S3V4 selon la façon dont les données sont transférées vers AWS (segment par opposition à flux). Créez le client comme suit:
var s3 = new AWS.S3({
signatureVersion: 'v4'
});
2) N'ajoutez pas de nouveaux en-têtes et ne modifiez pas les en-têtes existants. La demande doit être exactement telle que signée.
3) Assurez-vous que l'URL générée correspond à ce qui est envoyé à AWS.
4) Faites une demande de test en supprimant ces deux lignes avant de signer (et supprimez les en-têtes de votre PUT). Cela vous aidera à affiner votre problème:
ContentType: req.body.fileType,
ACL: 'public-read'
Si vous essayez d'utiliser une ACL, assurez-vous que votre rôle Lambda IAM a le s3:PutObjectAcl
pour le bucket donné et aussi que votre bucket permet le s3:PutObjectAcl
pour le principal de téléchargement (utilisateur/iam/compte qui télécharge).
C'est ce qui m'a corrigé après avoir vérifié tous mes en-têtes et tout le reste.
Inspiré par cette réponse https://stackoverflow.com/a/53542531/2759427