2022年1月23日日曜日

アプリケーションは300系リダイレクトするけどクライアントは実体を受け取れるようにする方法

 ウェブサービスを作っていると、大きなファイルを認証付きでダウンロードさせたいけど、ダウンロード処理自体はアプリケーションサーバーを経由したくない場合があります。

それだけならアプリケーションサーバーから300系のリダイレクトを使えばいいんですが、クライアント側に実体のURLを知られたくない場合もあります。

例えばこんな状況。

  • ECサイトでダウンロード商品を扱っていて、ダウンロードファイル自体はアプリケーションサーバーとは別のサーバーにある
  • 商品を購入済みの人にだけダウンロードを許可したいので、商品購入済みかどうかをダウンロード前にアプリケーション側でチェックしたい
  • でもダウンロード処理自体にはアプリケーションサーバーを経由したくない(例えば、ダウンロードのためにPHPのreadfile()とかで別サーバーのファイルを読みたくない)
  • かといってアプリケーションサーバーから300系リダイレクトをするとクライアントにファイルの実体のURLが知られてしまうので、リダイレクトはやりたくない(S3の署名付きURLのような上等なものは使えない)
つまり「ダウンロード要求をアプリケーションが受けるけど、アプリケーションからは実体を返したくない。でもクライアントには実体を届けたい」という困ったちゃんな要望。

Nginxで解決

色々調べたらNginxでできることがわかりました。とは言ってもNginxに専用の機能があるわけではなく、設定を工夫すればできるという感じです。

前提として、アプリケーションサーバーの前段にNginxがリバースプロキシーとして置いてある状態を想定します。

stack overflowの回答例をどうぞ。

server {
    ...

    location / {
        proxy_pass http://backend;
        # You may need to uncomment the following line if your redirects are relative, e.g. /foo/bar
        #proxy_redirect / /;
        proxy_intercept_errors on;
        error_page 301 302 307 = @handle_redirects;
    }

    location @handle_redirects {
        set $saved_redirect_location '$upstream_http_location';
        proxy_pass $saved_redirect_location;
    }
}

ここで、 http://backend はアプリケーションサーバーを指しています。

アプリケーションサーバーからの300系リダイレクト(301, 302, 307)に対して、Locationヘッダーの値をプロキシー先とみなしてproxy_passに設定し、実体を取りに行きます。なるほどなるほど。Nginxがアプリケーションとクライアントの仲介をすることで、一見矛盾する要望を見事実現しています。頭いいー。

これのいいところは、ダウンロードファイルのあるサーバーはNginxからアクセスさえできればいいので、インターネットに晒す必要がないということです。今回のような要望のあるファイルは不用意にダウンロードされたら困ることが多いので、外部に晒さずに済むのは大きいですね。

あと、クライアントから見たダウンロード用のURL(実際はアプリケーションサーバーのURL)を任意に決められるというメリットもあります。いかにもワンタイムURLですと言わんばかりのゴチャゴチャした文字列がつかず、スッキリとしたURLにできます。だから何だとか言わない。

ちなみに・・・

そもそもNginxを使えるなら、ダウンロードファイルのあるサーバーでngx_http_secure_download_moduleを使えば署名付きURLのようなものを発行できます。標準で入っている機能なので、特別な設定は不要です。

この場合はもちろんサーバーをインターネットに晒す必要があるので、うっかりミスで署名付きURLを無効にしないように。


0 件のコメント:

コメントを投稿