CSRF対策のhiddenパラメータとHTTPキャッシュ

CSRF対策として、hiddenパラメータにセッションIDを出力する手法がある。この手法を採用するとき、HTTPレスポンスのキャッシュはどう制御すべきだろうか。

非共有キャッシュは活用したい

まず、一切キャッシュさせない方針がありうる(Cache-Control: no-store)。これで問題ないのであれば、もう何も考える必要はない。今回の記事では、そうではなく、非共有キャッシュを活用する方針を前提としたい(Cache-Control: private)。REST厨だからだ。

ここから先は、キャッシュの有効期限を明示的に指定できるかどうかによって話が変わってくる。

有効期限を明示的に指定できる場合

有効期限を明示的に指定できる場合は、以下のようなレスポンスヘッダを返すのがよいだろう。

Cache-Control: private,max-age=1800
Vary: Cookie

Cache-Controlで、非共有キャッシュへのキャッシュを許可するとともに、有効期限を明示的に指定する。

重要なのは「Vary: Cookie」だ。指定しないと、再ログインによってセッションIDが変わったり、ログアウトによってセッションIDが無効になったりしても、有効期限内であればキャッシュが使われてしまう。

有効期限を明示的に指定できない場合

有効期限を明示的に指定できない場合は、以下のようなレスポンスヘッダを返すのがよいだろう。

Cache-Control: private,must-revalidate
ETag: "hogehoge"
Last-Modified: Thu, 13 Sep 2012 01:23:45 GMT

有効期限を明示的に指定できないのは、常に新鮮なレスポンスを返したいからだ。よって「Cache-Control: must-revalidate」を指定し、キャッシュの常時検証をクライアントに指示するのがよいだろう。

検証条件として、上記の例ではETagとLast-Modifiedを返している。少なくとも一方は返し、If-None-MatchやIf-Modified-Sinceを用いた条件付きGETに対応する必要がある。

「Vary: Cookie」がないのは、セッションIDの変化に応じてETagやLast-Modifiedが変わるのを前提としているためだ。気になるなら返してもよいだろう。

JavaScriptでキャッシュをさらに有効に使う

さらに考えると、セッションIDが変わっただけでキャッシュが無効になるのは、もったいない気がしてくる。hiddenパラメータへのセッションIDのセットをクライアントサイドで処理するのはどうだろうか。文脈は違うが「Kazuho@Cybozu Labs: CSRF 対策 w. JavaScript」や「畑@サイボウズ・ラボ - CSRF対策 with JavaScript」で紹介されている手法である。

メリットとして、レスポンスにプライベートな情報が含まれない場合に、共有キャッシュの使用が可能になる(Cache-Control: public)。

また、セッションIDが変わっただけではキャッシュの検証が不要になる。キャッシュのヒット率が上がるし、サーバ側はリソースの本質的な変化だけを気にすればよいから実装が楽になる。良いことずくめではないだろうか。

ハイパーリンクは「状態の表現」をつなぐ

RESTful Webアーキテクチャにおいて、a要素やform要素に代表されるハイパーリンクがつなぐものは、リソースではなく「状態の表現」である。例として、POSTメソッドのレスポンスを考えると分かりやすい。ユーザがとくに知りたいのはPOSTの結果がどうなったかだ。サーバは、結果を表すリソースのURIをLocationヘッダで返すことも可能ではあるが、レスポンス自体に結果を含めてもよい(むしろ後者のほうがよく見かける)。このときレスポンスは「POSTの結果がどうなったか」をあらわす状態の表現であり、リソースそのものではない。

GETでも事情は変わらない。そもそも、リソースは転送(Transfer)されないのだ。転送されるのは状態の表現(Representational State)でしかない。

URI are identifiers of resources.  GET is a request for a
representation of the current state of the identified resource.
The server does not transfer the resource itself because that's
not what the client requested; the client does not want the
mechanism that implements the resource over all time -- only the
current state of the resource at that instant in time.  That's
what allows the client, and the transfer protocol, to be simple.

Re: resource and representation from Roy T. Fielding on 2002-07-08 (www-tag@w3.org from July 2002)

リンクが含まれるのも状態の表現だし、リンクを通じて得られるものもまた状態の表現なのである。

先日「ハイパーリンクは何をつないでもいい」を書いた時点では、このことが理解できていなかった。

「接続性」が誤解のもと

ハイパーリンクはリソースをつなぐべき、と僕が誤解してしまったのは、『RESTful Webサービス』で定義された「接続性」(Connectedness)が原因かもしれない。同書100ページに「リソースの接続性」とあるし、128ページには「リソースからリソースへの移動」とも書かれている。著者の意図はともかく、これらの記述から、リソース同士をつなぐのはハイパーリンク、というメンタルモデルが僕のうちにできてしまった可能性は大いにある。

Roy T. Fieldingは、接続性の定義に批判的だ。「アプリケーション状態エンジンとしてのハイパーメディア」を「接続性」と呼び替えられると、ハイパーメディアの駆動力としての役割が不明確になってしまう、というのだ(On software architecture - Untangled)。「ROAはリソースを重視しすぎだ」とも言っている。その弊害のひとつが、僕の誤解に現れてしまったのかもしれない。

記事検索
Twitter