Kugelblitz

いつ何時誰の挑戦でも受ける!

Spring Securityでのログインフォームセッションタイムアウト問題に対応する

CSRF対策を行ったログインフォームで、セッションタイムアウトしてからログインを試みると、正しいIDとパスワードを入力しているにもかかわらず、ログインすることができません。CSRFトークンが無効になっているからです。

Spring Securityでの話ですが、CSRFトークンをセッションに保存するやりかたなら、他の実装でも起こるかと思います。

ギョームアプリだと、ログインフォームを開いて放置、のような使い方はまずないので、今まで有耶無耶にしていましたが、ちょっと調べてみました。

結論から言うと「フォームを送信する前にJavaScriptで有効なCSRFトークンを取得する」が正解のようです。Spring Securityの公式マニュアルに載っています。ログイン直前にCSRFトークン再取得か、そりゃそーだよな。

具体的には、サーバー側に、

@RestController
public class CsrfController {

    @RequestMapping("/csrf-token")
    public CsrfToken csrf(CsrfToken token) {
        return token;
    }
}

のようなエンドポイントを用意します。

クライアント側では、

$(function() {
    $('#login-btn').on('click', function() {
        e.preventDefault();
        $.ajax({
            url: '/csrf',
            type: 'GET'
        })
        .done(function(data) {
            $('input[name=_csrf]').val(data.token);
            $('#form').submit();
        });
    });
});

みたいに、ログインボタンがクリックされたらAJAXでCSRFトークンを取得して、フォームのhidden項目にセットしてからログインを試みればいいですね。

動かして試したわけではないですが、公式にもあるやりかたなので、うまく動くはず。

追記 2017.07.15

動かして試して見ましたが、一部追記があります。

まず、Spring設定ファイルに、

<sec:intercept-url pattern="/csrf-token" access="permitAll" />

が必要です。CSRFのトークンを、ログインしていない(認証されていない)状態でも取得できるようにしておく必要があります。

あと、

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

を追加して、コントローラの引数にCsrfTokenを指定したときにうまく処理されるようにする必要があります。

Pocket

他の記事