Communeソフトウェアエンジニアのu2です!
今回はビジネスロジックによって制御される画像配信の仕組みについて検討したので、その紹介をします。
はじめに
Webアプリケーションにおいてコンテンツの権限制御を細かく行う場合、テキストデータだけでなくそれに付随する画像やファイルの配信方法も同様の制御下に置かれるよう検討する必要があります。
例えばユーザー間におけるダイレクトメッセージのような機能であれば、ダイレクトメッセージ内のテキストは勿論そこに添付された画像も他者からは見れないよう制御されるべき情報になります。一般的に画像はCDNを活用しユーザーに配信すると思いますが、ダイレクトメッセージであれば、認証が通ったかだけではなくそのダイレクトメッセージにアクセスできるユーザーのみが画像を取得できるような、機能に応じたユーザー個別のビジネスロジックに従って配信制御をするためには自前で画像取得リクエストをプロキシして配信する仕組みが妥当だと判断しました。
課題
新機能の開発に伴い、ファイル配信について以下のような課題が明確になりました。
権限制御の複雑化
- プレーンコンテンツでは「未ログインでもアクセス可能な設定か」に基づく閲覧制限
- ダイレクトメッセージ機能では「購読しているか」に基づく閲覧制限
- グループ機能では「グループに所属しているか」に基づく閲覧制限
- XXX機能では...etc.
既存システムとの整合性
- 現行の画像配信システムへの影響を最小限に抑える必要
- 段階的なリリースを可能にする設計
- データベーススキーマの大幅な変更を避ける
パフォーマンス要件
- モバイルアプリを含む多様なクライアントへの対応
- ユーザー体験を損なわない画像配信システムの構築
これらの要件を満たすアーキテクチャを設計するために、複数のアプローチを検討することになりました。
検討したアプローチ
検証候補に挙がったのは大まかに以下の3つで、
- 署名付きURLの活用
- Nginxで画像配信用のプロキシサーバー構築
- 既存APIサーバーに実装、Cloud Runを別立てし専用化
最後の方針が最も良さそうだという検討結果になりました。
署名付きURLの活用
最初に検討したのはこの方法です。
sequenceDiagram participant Client participant API as API Server participant Auth as Authorization Logic participant Storage as Cloud Storage Client->>API: 画像URLリクエスト API->>Auth: 権限チェック Auth-->>API: OK API->>Storage: 署名付きURL生成 Storage-->>API: 署名付きURL API-->>Client: 署名付きURL Client->>Storage: 画像リクエスト Storage-->>Client: 画像データ
元々CDNを経由する画像URLを返そうとしていた箇所を、署名付きURLに差し替えるだけの案です。署名付きURLを得るためには画像URLを得られる状態である必要がある、つまりコンテンツへのアクセス権限を持っている状態であることが担保されています。
シンプルでとても良かったのですが、詳細な要件を調査していくと課題が見えてきました。短時間とはいえURLが分かれば誰でもアクセスできてしまいます。またこの署名付きURLに無関係の第3者がアクセスしたとしてもそれは不正アクセスに当たらないという専門家の見解もありました。
論文
参考
導入コストも高くなく実装面では良かったのですが
「セキュリティ要件満たせなくないか?」そんな声と共にこの案は見送りとなりました。
Nginxで画像配信用のプロキシサーバー構築
次に検討したのは Nginx を使ったプロキシサーバー構築案です。画像URLをプロキシサーバーに向いたものにし、画像取得リクエストに対して都度アクセス権限の確認をするイメージです。
sequenceDiagram participant Client participant Nginx participant Storage as Cloud Storage Client->>Nginx: 画像リクエスト Nginx->>Nginx: 権限チェック Nginx->>Storage: 画像取得 Storage-->>Nginx: 画像データ Nginx-->>Client: 画像データ
署名付きURLの方針と異なり、リクエスト毎にアクセス権限を見るためよりセキュアになっています。また Nginx なら一般的なアプリケーションフレームワークよりも大量のリクエストも捌け、APIサーバーとも分離できるためトラフィックが増えてもAPIサーバーへの影響なく運用が可能です。
しかしアクセス権限はかなりコアなビジネスロジックであり変更可用性も求められ、実際にロジックの変更頻度も低くありません。そのためAPIサーバーとNginxサーバーの両方でこのビジネスロジックを管理することは現実的ではありませんでした。かといってNginxがアクセス権限確認をAPIサーバーに問い合わせるなら、結局APIサーバーの負荷は変わりません。
もしかするとアクセス権限に関する機能をマイクロサービス化することができれば解決できたかもしれませんが、様々な機能と紐付く概念なため、それら全てを分離するには全体の大幅な変更が必要なため現実的ではありませんでした。
「コアロジックが分散してしまって保守性が最悪になりそう」 そんな声が上がり、この案も見送ることになりました。
既存APIサーバーに実装、Cloud Runを別立てし専用化
最終的にこの方針に決定した案です。
- 既存APIサーバーのコードに同居する形で画像配信ロジックを実装 → 保守性が高い
- アクセスの都度既存のアクセス制御ロジックを通す → 適切なユーザーにのみ配信でき、よりセキュア
- APIサーバーとは別にサーバーを立て、画像配信のリクエストとなるパスを全てこの別サーバーに流す → トラフィック増でも既存APIサーバーに影響しない
最終的なリクエストのフローはこんな感じです。
sequenceDiagram participant Client participant LB as Load Balancer participant API as API Server<br/> for Images participant Optimizer as Image Optimizer<br/>/ Cacher participant Storage as Cloud Storage Client->>LB: 画像リクエスト LB->>API: /api/images/*<br/>専用サーバー転送 API->>API: 権限チェック API->>Optimizer: 画像リクエスト Optimizer->>Storage: 画像取得 Storage-->>Optimizer: 元画像 Optimizer->>Optimizer: UAに応じた最適化/キャッシュ Optimizer-->>API: 最適化済み画像 API-->>Client: 画像データ
前2つの案から飛躍している部分もいくつかありますが、少しずつ検討した結果、大元の方針からこのようなフローなら全ての要件を満たせることがわかりました。フレームワークの都合でNginxよりは圧倒的に捌けるリクエスト数は少ないのですが、しばらくは水平スケーリングでどうにかなる算段です。
その他検討したこと
全体的なアーキテクチャの他、細かい設計もいくつか考慮・断念したものがあります。
- Mediaテーブルを作りアクセス権限の一律管理を状態として持ち、権限制御ロジックを手前に寄せる
- エッジワーカーで認証処理を行いキャッシュしつつ画像を配信する
- 署名付きcookieを元にファイルアクセスをする
どれも他の方針検討中に出たNG要件がクリアできずアイデア段階で終わりました。
おわりに
画像配信は基本的にCDNを使うのが一般的で、今回のようなことを検討する際にあまりオープンな情報が得られず苦戦していました。しかし検討を進めていく中で弊社独自の課題があるため、汎用的な解決策はないことがわかりました。
どの選択肢にもトレードオフがあり私たちの場合は、一定以上の「セキュアさ」を持つ中で「既存システムとの整合性」と「保守性」を重視した結果、今の設計に落ち着きました。別の組織、別の要件であれば、違う選択肢が正解になるかもしれません。
制約の多い現実的な環境で、どうやって理想と現実の折り合いをつけるかという点で、私たち自身も良い学びを得ることができました。技術選択は難しいけど楽しい!
コミューンでは、私たちと一緒に働く仲間を募集しています!少しでもコミューンの開発組織や職場環境に興味をお持ちの方、ぜひカジュアル面談でお話ししましょう。
https://open.talentio.com/r/1/c/commune/pages/108399/apply?token=0e6WjaZ62wkVoZ1XtgFduSzwPNHrKSR6