ブラウザでキャッシュできてアクセス制限できるAWSでの静的ファイルの配信について考えてみた

こんちは、調べたことをメモしておきます

状況と課題

  • SPAで構成されたwebサービス
  • ログインしたユーザーは画像や動画などの静的ファイルを他の指定したユーザーに共有できる
    • 他のユーザーは共有されていない状態では静的ファイルにアクセスできてはいけない
  • 画像や動画は、S3から配信されている。
  • 仕組み
    • 1.ページ表示時にブラウザがAPIサーバーに静的ファイルのリクエスト
    • 2.APIサーバー側で指定の静的ファイルに対しPresigned URLを発行
    • 3.APIサーバー側がそれをブラウザにレスポンス
    • 4.ブラウザ側で画面に静的ファイルを表示
  • ブラウザで静的ファイルがキャッシュされず、ページ移動のたびにネットワークリクエストが送られる
    • APIサーバー側が発行するPresigned URLは毎回クエリパラメータが異なってしまうため

対策

  • 1.署名Cookieを利用する
    • S3のPresigned URLの代わりにCloudFrontの署名Cookieの機能を利用し、静的ファイルにクエリパラメータが付与されないようにする
    • 該当のCookieでアクセスできるS3のパスを全てAPIサーバー側で指定する必要がある
      • S3のパスの形式がアクセス範囲に沿っていない場合、アクセスできる静的ファイルのS3のパスを全てAPIサーバー側で列挙しなければならない
    • Cookieを利用できるようにCloudFrontのドメインの調整が必要
    • ブラウザが画像を表示する前に、APIサーバーのいずれかのエンドポイントでSet-Cookieが必要
    • 署名が一度だけで済むためAPIサーバー側の負担が減る
  • 2.Presigned URLのtimestampを丸めてクエリパタメータが一致するようにする
    • https://advancedweb.hu/cacheable-s3-signed-urls/

    • インフラ的には一番簡単な修正
    • APIサーバーのPresigned URL生成処理に対して大きな修正が必要
    • 実装を間違えた際に静的ファイルにアクセスできなくなる李kスクがある
    • 現状のようにS3のみを利用した場合でもCloudFrontを前段に配置した場合でも問題なく動作する
  • 3.独自の署名システムを作る
    • https://aws.amazon.com/jp/blogs/news/jpmne-secure-content-using-cloudfront-functions/

    • CloudFrontの前段にCloudFrontFunctionsを用意
    • APIサーバーからは、画像のクエリパラメータ/Cookieに独自の署名を付与する
    • CloudFrontFunctionsで署名を検証し、失敗したらエラー
    • (Cookieでやる場合)Cookieを利用できるようにCloudFrontのドメインの調整が必要
    • (Cookieでやる場合)ブラウザが画像を表示する前に、APIサーバーのいずれかのエンドポイントでSet-Cookieが必要
    • (Cookieでやる場合)署名が一度だけで済むためAPIサーバー側の負担が減る
  • 4.署名Cookieを利用しつつ、制御範囲をゆるくする
    • 案1ではユーザーごとに制御範囲を厳密に考えていたが、この案ではそれを緩める
    • 例. ログインしているユーザーであれば全ての静的ファイルが閲覧可能とする
      • 以下の仕様を満たせなくなるがそれが許容できるかどうか
        • 他のユーザーは共有されていない状態では静的ファイルにアクセスできてはいけない

  • 5.SPA側で、静的ファイルのPresigned URLを使い回す
    • APIサーバーからPresigned URLを取得した際、Presigned URLを再利用できるようメモリ上に保存しておく
      • キーがS3のパスで、バリューがPresigned URLになってるキーバリューストアを保存するイメージ(下記コード)
    • APIからPresigned URLが返却されたら、S3のパスを抽出し、それに合致するバリューをキーバリューストアに問い合わせるイメージ
    • トータルの修正コストは小さそう
    • 有効期限が切れたPresigned URLが保持され続けないように注意が必要
    • 署名の頻度が減るためAPIサーバー側の負担が減る
{
  "users/img/hoge.png": "https://hogehoge.s3.amazonaws.com/users/img/hoge.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAZ45HG4FIM7JVD7FE%2F20240914%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20240914T025733Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=971c45613bab710812952c0bc107a9aebe27395e874f03b2c239b815d91daefd",
  // ...
}

雑まとめ

うまい具合にまとめようとしたけど、状況によって最善の選択が変わってしまうので何も書けない…

tech  AWS  CloudFront  S3 

See also