カジュアルな技術ノート

小難しい技術のお話をできるだけわかりやすく...

【Spring Security】Spring Security の csrf トークンの仕組み

f:id:shin-kinoshita:20190406183111p:plain
Spring で開発した web アプリケーションの csrf 対策を行うときは Spring Security のトークン生成機能を利用することが多いと思います。
今回はこの csrf トークン生成の流れや仕組みを扱っていきます。

この記事で扱うこと

csrf トークン付与の方法

以下、すでに Spring Security をアプリケーションに導入済みという前提で話を進めます。

Spring Security を有効にしていればデフォルトで csrf トークンは生成されるようになっています。
必要なことは生成されているトークンを POST 時に送信されるようにリクエストに付与するだけです。

次にサンプルコードとして、csrf トークンが付与された POST リクエストを送信する form タグを Thymeleaf で書きます。

<form method="post" action="/csrf/passed">
  <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
  <input type="submit" value="submit" />
</form>

また form タグならば次のように th:action を使用するだけで付与することもできます。

<form th:action="@{/csrf/passed}" method="post">
  <input type="submit" value="submit" />
</form>

どちらのコードも次のようにレンダリングされます。

f:id:shin-kinoshita:20190406182703p:plain

_csrf.parameterName と _csrf.token

ここまでで csrf トークンの付与の方法はわかりましたが突然 _csrf..... といった見覚えのない変数が出てきましたね。。
これは一体どこから現れたのでしょうか?
この _csrf は HttpServletRequest の属性値に格納されているオブジェクトです。
よって controller クラスでもこれらの値を参照することができます。

@GetMapping
public String index(HttpServletRequest request) {
    CsrfToken csrf = ((CsrfToken)request.getAttribute("_csrf"));
    System.out.println(csrf.getParameterName());
    System.out.println(csrf.getToken());
    return "index";
}

Thymeleaf でも同じく HttpServletRequest からこれらの値にアクセスしていたというわけです。

csrf トークンの裏側の仕組み

ここまでは view や controller クラスでの csrf トークンの扱いを見てきましたが、普段の開発で意識しないより裏側の部分を見ていきます。

Spring Security の csrf トークンの裏側での処理を追うにあたって、登場人物は主に次の二つです。

  • CsrfFilter
    フィルター処理で csrf トークンに必要な諸々の処理を担当しているクラス
  • CsrfTokenRepository
    csrf トークンを生成したり保持したりしているクラス

裏側の処理を追うなら CsrfFilter クラスを見ていけばいいですが、内部で CsrfTokenRepository クラスを参照しています。
具体的には CsrfFilter が行う処理は以下の二つです。

トークンを HttpServletRequest の属性値に格納

すでに触れた HttpServletRequest に csrf トークンを格納する処理です。
csrf トークンの値自体は CsrfTokenRepository から取得しています。

csrf トークンの照合

ここまでは csrf トークンを埋め込む部分に関する話でしたが、無事に埋め込んだトークンがちゃんとリクエストで送信されているかどうかの確認処理です。
この csrf トークンの照合は更新系の Http メソッドのみ(GETHEADTRACEOPTIONS 以外)で適応されます。
リクエストで飛んできた csrf トークンと CsrfTokenRepository のトークンを比較し、同じであれば controller クラスに到達し通常通り view を返します。
異なれば例外をスローし、デフォルトではエラー画面が表示されます。

まとめ

簡単ながら csrf トークン付与の方法と裏側の処理の話を書きました。
csrf トークン周りに関してブラックボックスに感じている人がいれば参考になればと思います。