AWSのEC2でデプロイしようとしたら、ドメインが違うためできなかった話
はじめに。
自分はこう理解した!というていで書いているので、間違いや補足が必要な点があったらぜひコメントいただけますと嬉しいです。
やりたかったこと
前提
- ローカル環境でフロント(Vue.js)とバック(Java,SpringBoot)に分けて開発。
- 作成したローカルのプロジェクトをデプロイしたかった。
- ローカルではAPI通信に成功していた。
やりたかったこと
- フロントエンドとバックエンドを分けて、それぞれEC2インスタンスにデプロイ。
- フロントエンドはパブリックサブネットに、バックエンドはプライベートサブネットに配置。
- DBは RDSを使用。
こうしたかった
フロントとバックが通信できない問題にぶつかる
RDSとEC2インスタンスを作成し、フロントとバックのプロジェクトをインスタンス上で動くところまで確認。vpcのルートテーブルやインスタンスのセキィリティグループの設定もして、あとはAPI通信ができればOK!と意気込み通信したところ失敗。なぜ。。
原因は、cookieがセットできなかったからでした。
cookieの仕様として、ドメイン単位でブラウザに保存されるらしく、
ローカル環境ではフロントもバックも同じドメイン(localhost:ポート番号)になっており、ドメインが同じなのでcookieが共有できてしまっていた模様。
なぜcookieがセットできないと通信に失敗するのかは、実装の仕様としてcookieにセッションIDとcsrfトークンをセットしていたためです。
・セッションID = セッション管理に使っていました。
・csrfトークン = csrf対策用のトークンを保持するために使っていました。(特定の通信をするとき、このトークンをリクエストヘッダーに入れることで、処理が許可される。)
[補足]
以前は、Cookieは既定ですべてのリクエストに送信されていましたが、最近標準規格が変更されたらしく、SameSite 属性が設定されていない場合、 Cookie は Lax として扱われるそうです。
Laxの時、リクエストにcookieが付与される条件としてmethodが GET, HEAD, OPTIONS, TRACE であることが含まれています。私はPOST処理で試していたため失敗し続けたのかぁ
解決策
解決策としては、バックエンドでcookieのオプションの設定をする。
https通信で通信すること、且つcookieを異なるドメインでも共有される設定にすることで解決する模様。
- useSecureCookieをtrueにする。
- sameSiteをnullにする。
(Javaはバックエンドの方に任せてしまっているので明確なこと言えずごめんなさい)
【バックエンド分からんって人の回避策】
私みたいにバックのコード分からん!って人用の回避策としては、オリジンを分けることを諦めるです。
私が実際にした策としては、Javaリソースの中にフロントリソースを入れてビルド、それをEC2インスタンス(パブリックサブネットのみ)にデプロイしました。インフラからしたら怒られそう、、
SpringBootの静的コンテンツを参照
結局こうなった
[余談]ざっくり理解のcsrfトークンとCORS/Same-origin Policy/sameSite属性について
csrf? CORS? 聞いたことあるけどちゃんとは理解していないなぁという状態だったのですが、
今回のことでここらへんの理解が少しは進んだので自分のために記録しておきます。
ここからはほんとに自分はこう理解したけどという体裁なので、有識者の方がいればバンバンご指摘ください。
◾️Same-origin Policy(同一オリジンポリシー)
同一オリジンポリシーとは、基本的には同じオリジン(URL)でないと通信できないよーというブラウザのデフォルト仕様。異なるサイトからのリクエストを許可してしまうと悪意のあるサイトからの悪意のある通信も通ってしまうことを防ぐため、セキュリティの観点で備わっている機能。
じゃあどうやってクロスサイト(異なるオリジン)で通信するの?
- CORSを使用する
- リンクやリダイレクト、フォームの送信などはもともと許可されている
ここでCORSの登場です。
◾️CORS
CORSは異なるドメイン間でのHTTPリクエストを許可するための仕様。
CORSの設定で、異なるオジリンでも許可するサイトを指定することができます。
httpリクエストをした場合、プリフライトという本番のリクエストの前にこれからリクエストするけどいい〜?という通信が発生します。このプリフライトの情報から、リクエストを受け取ったサーバーはリクエスト元のサイトは許可されているのかどうかチェックし、OKであれば本番のリクエストをすることができます。
ただ、このプリフライトリクエストは単純リクエストの時には発動しません。
この単純なリクエストに該当するのが、よく使う機会があるForm等です。Formを通したリクエストはプリフライトが飛ばすオリジンチェックが入らないので、リクエストは成功してしまいます。
そんな時活躍するのがcsrfトークン。
◾️csrfトークン
Formを使うようなリクエストには、サーバー側でcsrfトークンを持ってないリクエストは許可しないよーという設定にすることで悪意のあるリクエストを防ぐことができます。
ざっくりの流れ
①ブラウザからトークン発行を要求する
②(サーバー側であらかじめトークン発行を許可するオリジンを決めておいて、)
許可するオリジンからの要求なのかチェックする。OKならトークンを返す。
③取得したトークンをつけてリクエストを送る → 正しいトークンなら成功!
私の場合、ここまでちゃんとバックエンドの方が実装してくださっていました。
何も知らずにAPIリクエスト送りまくってました、すみません。ありがとうございます。
ただ最近cookieのsameSite属性の標準仕様が変わり、デフォルトの場合POSTリクエストにはcookieがつかないため失敗し続けたということでした。そして開発時にはたまたまというかドメインが同じだったためcookieの仕様に気づかず過ごしてしまった。
AWSの勉強もかねてと思い始めたデプロイでしたが、それ以上の勉強になりました。
実際の現場ではどのようにセキュリティ対策しているのか気になるので、現場経験されている方のご意見お待ちしております!
参考サイト:
Discussion