署名付き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が表示され、フロントエンドに渡されます。次に、フロントエンドで、axiosと署名付き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
事前に署名されたs3 putアップロードで403 Forbiddenエラーを受信することは、すぐには明らかではないいくつかの理由で発生する場合もあります。
ワイルドカードはサポートされていないため、image/*
などのwildcardコンテンツタイプを使用して事前署名済みのURLを生成すると発生する可能性があります。
コンテンツタイプが指定されていないで事前に署名されたプットURLを生成し、ブラウザからアップロードするときにコンテンツタイプヘッダーを渡すと、この問題が発生する可能性があります。 URLの生成時にコンテンツタイプを指定しない場合は、アップロード時にコンテンツタイプを省略する必要があります。 ppy のようなアップロードツールを使用している場合は、コンテンツタイプヘッダーを指定しなくても自動的に添付される場合があることに注意してください。その場合、コンテンツタイプヘッダーを空に手動で設定する必要があります。
いずれの場合でも、ファイルタイプのアップロードをサポートする場合は、ファイルのコンテンツタイプをAPIエンドポイントに渡し、クライアントに返す事前署名付きURLを生成するときにそのコンテンツタイプを使用するのがおそらく最善です。
たとえば、APIから事前に署名されたURLを生成します。
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()
}
})
詳細については、次の2つのスタックオーバーフローの投稿もご覧ください: how-to-generate-aws-s3-pre-signed-url-request-without-knowing-content-type および S3。 getSignedUrlは複数のcontent-typeを受け入れます
1)データがAWSに転送される方法(チャンクとストリーム)によっては、S3V4署名を使用する必要がある場合があります。次のようにクライアントを作成します。
var s3 = new AWS.S3({
signatureVersion: 'v4'
});
2)新しいヘッダーを追加したり、既存のヘッダーを変更したりしないでください。リクエストは、署名されたとおりである必要があります。
3)生成されたURLがAWSに送信されているものと一致することを確認します。
4)署名する前にこれらの2行を削除する(そしてPUTからヘッダーを削除する)テストリクエストを作成します。これはあなたの問題を絞り込むのに役立ちます:
ContentType: req.body.fileType,
ACL: 'public-read'
ACLを使用しようとしている場合は、Lambda IAMロールにs3:PutObjectAcl
指定されたバケットと、バケットでs3:PutObjectAcl
アップロードプリンシパル(アップロードするユーザー/ iam /アカウント)。
これは、すべてのヘッダーと他のすべてを再確認した後で修正されたものです。