CSRFとプリフライトリクエスト
PlayFrameworkは通常の入力フォームからのリクエストはCSRFFileterが常備されていてhelperでform生成している限り問題なく対策できるんだけど、JSからのPOSTメソッドなんかにCSRFTokenを付与して検証するのは結構手間。
いくらヘッダーにAccess-Control-Allow-Originで、クロスオリジンを許可していなくても、届いたリクエストはサーバ側で処理した上で、「レスポンスをブラウザ側のJSが読めない」だけなのでCSRFの対策とはならない。
で、最近はクロスオリジン(cors、Cross-Origin Resource Sharing)でJS等がリクエストする時は、予めサーバ側にOPTIONメソッドで送信可能であるか?(プリフライトリクエスト)を確認することでCSRFの対策を施すことが出来るようです。
PlayFrameworkは標準でCORSFilterが搭載されていて簡単に透過的に検証することができる。
CORSFilterを有効にする
サイトによってはbuild.sbt
に
libraryDependencies += filters
として依存解決するよう定義しているページもあるが、現PlayFrameworkのバージョン2.8では多分不要。
application.conに以下のようにすることでCORSFilterが有効になり、プリフライトリクエストが定義に従って検証される。デフォルトの定義は上記のドキュメントで確認できる。
application.conf
# この記述でデフォルトの定義で自動的に検証される
play.filters.enabled += "play.filters.cors.CORSFilter"
設定を変更する場合は同じくapplication.conf
で
play.filters.cors {
allowedOrigins = ["http://domain.com:8080"]
}
のように記述すればよい。
このフィルターの定義に引っかかったリクエストはControllerにDispatchされず、Failureのレスポンスがクライアントに返される。故にControllerに定義された更新等の処理が行われない。
尚、CORSFilterを通過したリクエストには、自動的にAccess-Control-Allow-Originが付与されたレスポンスヘッダーが返されるのでController側で、withHeaders()
を記述する必要はない。
JSからプリフライトリクエストを送信
JsonをPOSTで送信すると自動的にプリフライトリクエストを送信する。と思う。
axios.post("http://domain.com", {})
この時、僕の環境のChrome developer toolなんかではOPTIONメソッドの送信が表示されていなくてハマりにハマったので、みんなの環境でも注意して欲しい。
検証時の疑問点
僕が検証した時は、プリフライトのみならずJSからのクロスオリジンでのajax GETリクエストなんかもFilter対象になっていた。
GETは全てのオリジンから許可してPOSTのプリフライトのみオリジンを検証するといったことはひょっとして出来ないかもしれない。
検証当時はハマってた笑
こうやって整理するととても簡単な設定なんだけど、設定時は色々とハマってて笑う
んー。Playframeworkでfilters.cors触ってるんだけど、Access-Control-Allow-Originヘッダー吐いてくれない。
— Fumiya Ichikawa (@LET__IT__RIDE) April 27, 2020
ControllerでwithHedersでやると吐くんだけど、filters.corsの理解間違ってるのかな。
axiosでプリフライトリクエストどうやって送るの? 笑
— Fumiya Ichikawa (@LET__IT__RIDE) April 27, 2020
ああ、プリフライト送信されてるっぽいけど、Play側で蹴ってるか。
— Fumiya Ichikawa (@LET__IT__RIDE) April 28, 2020
POSTに場合は'Content-Type'をjsonなりxmlにしないと送信されないっぽい。
play側で
— Fumiya Ichikawa (@LET__IT__RIDE) April 28, 2020
play.filters.enabled += "play.filters.cors.CORSFilter"
がないとプリフライトのリクエストは全部rejectだね。
なるほど。
— Fumiya Ichikawa (@LET__IT__RIDE) April 28, 2020
整理すると
play側は
play.filters.enabled += "play.filters.cors.CORSFilter"
しないとフィルタリング + Access-Control-Allow-Originを返さない。プリフライトのOPTIONメソッドだけではなくGETのメソッドもフィルタ対象にしているっぽい。
JS側は
— Fumiya Ichikawa (@LET__IT__RIDE) April 28, 2020
POST + 'Content-Type: application/json'とかでプリフライトリクエストを送信する。
しかし、自分の環境ではchrome developer toolからOPTIONメソッドのキャプチャが表示されていなくて、あたかもPOSTが失敗してたように見えた。