HTML FormでS3にPOST
2018-02-24
AWSのリファレンスを参考に、FormでS3バケットへのPOSTを試してみました。
準備 #
FormでS3にPOSTするためには、各フィールドへ送信するファイルに合わせて、POST Policy、Signature、アクセスID、その他ファイルに関する情報を設定する必要があります。
(POST Policy:POSTする時につける設定情報、Signatureはそれをシークレットキーでhashしたもの)
バケットポリシー #
フィールドにアクセスIDを設定しますが、このアクセスIDに対応するIAMユーザーは、POST先のバケットに対する操作権限が必要です。
基本的なAWSのIAMユーザーの考えですね。
次のようにバケットポリシーを設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"aws": "arn:aws:iam::<12桁の数字>:user/<IAMユーザー名>"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::<バケット名>/*"
]
}
]
}
ここでは全ての権限与えておりますが、実際に使う場合はActionやConditionブロックで制限したほうがよいかと思います。
フォーム #
続いてフォーム。multipart/form-data
で、アップロードするファイルの属性や認証情報をPOSTします。
各パラメータについては
リファレンスの[HTML Form Fields]をご参考。
<form action="http://<bucket name>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<label>
S3 Tags for File: <input type="text" name="x-amz-meta-tag" value="" /><br />
</label>
<label>
Content-Type: <span>image/png</span>
</label>
<!-- AWSが${filename}に選択したファイル名を入れてくれる -->
<input type="hidden" name="key" value="from-form${filename}" />
<input type="hidden" name="acl" value="private" />
<input type="hidden" name="success_action_redirect" value="[任意のURL]" />
<input type="hidden" name="x-amz-server-side-encryption" value="AES256" />
<input type="hidden" name="Policy" value='[POST PlolicyをBase64エンコードした値]' />
<input type="hidden" name="X-Amz-Signature" value="[Policyをもとに作成したSignature]" />
<input type="hidden" name="X-Amz-Credential" value="/20180219/ap-northeast-1/s3/aws4_request" />
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Date" value="20180219T000000Z" />
<input type="hidden" name="Content-Type" value="image/png" />
<label>
File: <input type="file" name="file" /> <br />
</label>
<!-- The elements after this will be ignored -->
<!-- submitより後にinput要素を置いても送信できないので、submitは一番最後に書く。 -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
試していて、次のようなところで手間取りました。
ただの私の確認不足なのですが……
Signature生成 #
POST PolicyをBase64エンコードしたものをPolicyというFieldに指定。
それをStringToSignとして、認証情報(Signature)を生成しFieldにのせます。
生成手順を参考にPHPで実装したのですが、各処理ステップでキー文字列を16進数文字列に変換してしまい、上手く認証が通らず?になってました。
正しくは、キーをバイナリデータとしてそのまま渡せばOK。16進数文字列にするのは生成したSignatureをFormのフィールドに指定する時だけ。
function makeSignatureKey($awsSecretKey, $dateStamp, $regionName, $serviceName) {
// 間違い
// $kDate = bin2hex(signHash('AWS4' . $awsSecretKey, $dateStamp));
// $kRegion = bin2hex(signHash($kDate, $regionName));
// $kService = bin2hex(signHash($kRegion, $serviceName));
// $kSign = bin2hex(signHash($kService, 'aws4_request'));
$kDate = signHash('AWS4' . $awsSecretKey, $dateStamp);
$kRegion = signHash($kDate, $regionName);
$kService = signHash($kRegion, $serviceName);
$kSign = signHash($kService, 'aws4_request');
return $kSign;
}
Fieldの位置 #
各フィールド、POST Policyも大丈夫そうなのに、[key]フィールドが不足というエラーが発生。
原因は、コードをととえている時にsubmitの位置を上にしてしまい、それ以降に書いているパラメータを送れていないからでした。
<form action="http://<bucket name>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
<!-- submit以降のフィールドが無視されるため、パラメータ不足エラーとなる。 -->
<label>
S3 Tags for File: <input type="text" name="x-amz-meta-tag" value="" /><br />
</label>
<label>
Content-Type: <span>image/png</span>
</label>
<input type="hidden" name="key" value="from-form${filename}" />
...
</form>
リファレンスを見なおすと、<!-- The elements after this will be ignored -->
とちゃんと書いてありますね。恥ずかしい。
サンプルコードは こちら
単純なPOSTだとPOST Policyのフォームが楽そうですが、少し込み入った処理(容量が大きいファイル送信など)をする場合は、JavaScriptのSDKを使ったほうがよさそうです。
また試してみよう。