LoginSignup
14
12

More than 5 years have passed since last update.

署名付きURLでAWS S3にファイルをアップロードする際に気をつけること

Posted at

署名付きURLを用いて、S3にファイルをアップロードした際に、なぜかアップロードしたファイルの中身だけでなく、変なヘッダーのようなものが付与された形で保存されている問題を解決した話です。

目次

  • 署名付きURLとは?
  • 発生した問題
  • 原因
  • 解決策

署名付きURLとは?

AWS S3ではバケット単位やIAMユーザ単位でアクセス制御を行うことができますが、その一環として、有効期限を設けてある特定のURLからアクセスできるようにする機能です。

本来なら、ブラウザからサーバー経由でファイルをアップロードして、S3バケットに保存するという流れが一般的かと思います。なのでサーバー側で、認証や分割ファイルの結合といった処理が行えます。

署名付きURL と使うとサーバーを経由せず直接S3にアップロードできるため、運用しているサーバーに負荷をかけずに楽に運用できます。実装も楽です。特定のURLにアップロードすれば良く、前者に比べるとセキュリティに弱いため、有効期限を設定できます。

数GBの大きなファイルでも自社サーバーを経由せずに直接アップロードできるため、サーバーが落ちる心配がなく安心です。

発生した問題

PUT権限の署名付きURLにブラウザからJSライブラリの superagent を用いてCSVファイルをアップロードしていたのですが、何故かS3にアップロードされたデータに意図しない変な情報が付与してしまいました。

id,name
1,pokohide
2,hogehoge

が署名付きURLにファイルをPUTすると、以下のような形式で保存されていました。

———WebKitFormBoundaryO5quBRiT4G7Vm3R7
Content-Disposition: form-data; name="message"

id,name
1,pokohide
2,hogehoge
------WebKitFormBoundaryO5quBRiT4G7Vm3R7

実際にアップロードに際し使っていたコードの例がこちら

upload.js

import request from 'superagent'

  ...
  request
    .put(presigned_url)
    .attach('data', file)
    .('progress', (e) => { console.log(e) })
    .end((err, res) => {
      if (err) console.log(err)
      else console.log(res)
    })

上記のコードの file はINPUTから入力のあったファイルデータをそのまま渡していました。これで行けると思っていたのに・・・

原因

署名付きURLでファイルをアップロード(PUT)すると、S3側はPUTされた HTTP Body の中身をそのまま保存しているそうです。

なので、PUTする前に HTTP Body がどのタイミングで意図しない形式になっているかを調査しました。結果、原因はFormData APIにありました。

ブラウザが進化するにつれて複数の種類(csvimageなど)のデータを一度に扱える形式(multipart/form-data)が生まれ、この形式でフォーム送信された場合は内部的にHTTP Bodyが FormData オブジェクト形式で送信されます。

この際に、もともと複数ファイルに送れるように拡張された形式であるため、今回のように一つのファイルを送信するときでも勝手にHTTPボディに

———WebKitFormBoundaryO5quBRiT4G7Vm3R7
Content-Disposition: form-data; name="message"

{中身}
------WebKitFormBoundaryO5quBRiT4G7Vm3R7

というように、意図した中身だけでなく、サーバー側でに複数ファイルが送られたときに判別できるように区切り用の識別子のようなものが付与されてしまっていました。

今回の問題は、このようにHTTPボディが勝手に改変されて、それがそのままS3に保存されていたために起きていた問題であることが判明しました。

また、もともと使っていた superagent というJS製のHTTPクライアントがFormDataのラッパーであったため、何をしようにもContent-Type: multipart/form-dataになってしまっていたことで上記の問題が引き起こされていました。なので、このライブラリとFormDataを使うことを諦め、 XMLHttpRequest でファイルをアップロードすることにしました。

superagent でも Content-Typemultipart/form-data 以外に変えれば上手く行ったかも知れませんが検証しませんでした。

参考URL

解決策

upload_with_xhr.js

...

let xhr = new XMLHttpRequest()
xhr.open('PUT', url, true)
xhr.setRequestHeader('Content-Type', file.content_type)
xhr.setRequestHeader('X-FILE-NAME', file.name)

xhr.onload = function(e) {
  if (this.readyState === 4) { // DONEを検知
    console.log('upload done')
  }
}

xhr.onerror = function(e) {
  return reject(e)
}

xhr.upload.onprogress = (e) => {
  if (e.lengthComputable) { console.log('pregress') }
}

xhr.send(file)

このように、Content-Typemultipart/form-data ではなく、アップロードするファイルの Content-Type をヘッダーに与えて、送信すればHTTPボディが変わることなく、意図した形式でデータがS3にアップロードされました。


参考URL

14
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12