SpringSecurityを使ったログアウトでセッションが破棄されない場合

# SpringSecurityで認証を設定する

SpringSecurityを使うと、Webアプリケーションの認証機構をSpringSecirityに一任できます。例えばログインやログアウトのURLや、CSRF(クロスサイト・リクエスト・フォージャリ)を回避するトークンの発行も簡単に設定可能です。

## SpringSecurityの設定例

今回は以下を想定した設定にします。

* FORM認証を使う
* CSRFの回避を自動で行う
* Spring4シリーズで統一する

```xml:applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
  xmlns:sec="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/security
  http://www.springframework.org/schema/security/spring-security.xsd">

  <sec:http auto-config='true'>
    <!-- CSRF回避チェックを有効 -->
    <sec:csrf/>

    <!-- アクセス制御をしないURLを定義 -->
    <sec:intercept-url pattern="/login*" access="permitAll"/>
    <sec:intercept-url pattern="/css/**" access="permitAll" />
    <sec:intercept-url pattern="/fonts/**" access="permitAll"/>
    <sec:intercept-url pattern="/images/**" access="permitAll"/>
    <sec:intercept-url pattern="/js/**" access="permitAll"/>
    <sec:intercept-url pattern="/templates/**" access="permitAll"/>

    <!-- ロールに ROLE_ADMIN を持つユーザID(USERNAME)のみアクセス許可 -->
    <sec:intercept-url pattern="/*" access="hasRole('ROLE_ADMIN')" />

    <!-- FORM認証の振る舞い -->
    <sec:form-login
        login-page="/login.action"
        login-processing-url="/j_spring_security_check"
        username-parameter="j_username"
        password-parameter="j_password"
        authentication-failure-url="/loginFailure.action" />

    <!-- ログアウト時の振る舞い -->
    <sec:logout 
        logout-url="/logout"
        logout-success-url="/logout.action"
        invalidate-session="true" />
  </sec:http>

  <!-- データベースを使った認証管理 -->
  <sec:authentication-manager alias="authManager">
    <sec:authentication-provider>
      <sec:jdbc-user-service data-source-ref="dataSource"
        users-by-username-query="
          SELECT
            USERNAME
          , PASSWORD
          , ENABLED
          FROM
            USERTABLE
          WHERE
            USERNAME = ?"
        authorities-by-username-query="
          SELECT
            USERNAME 
          , AUTHORITY 
          FROM 
            ROLETABLE 
          WHERE 
            USERNAME = ?"
      />
    </sec:authentication-provider>
  </sec:authentication-manager>
  <beans:bean 
    id="sessionRegistry" 
    class="org.springframework.security.core.session.SessionRegistryImpl" />
</beans:beans>
```

SpringSecurity4ではSpringELを使った設定になります。3.xまでの記述では動作しません。

## ログアウトのURLを選択しても、実はセッションが残っている場合がある

さて、先ほどのxmlにてログアウト時のURLは、/logout と定義しました。
この設定そのものは間違いないのですが、CSRF回避チェックを有効にしている場合は、アンカータグやリダイレクトで /logout を指定すると、確かにログアウトページを表示しますが、なんとセッションが破棄されません。

この理由は **CSRFチェックが有効だから** です。
個人的にはややもんにょりしますが (´・ω・`)

このため、ログアウトを行う際には必ず次のルールを守ります。

* \<form\>を使う
* HTTP-METHODはPOST必須
* CSRFチェックの送信パラメータを設定する

実装例は以下のようになります。
送信ボタンなどはつけずに、JavaScriptから送信する例です。

```html:header.jsp(+JSTL)
<c:url var="logoutUrl" value="/logout"/>
<form id="logoutForm" action="${logoutUrl}" method="POST">
  <input type="hidden" 
    name="${_csrf.parameterName}" 
    value="${_csrf.token}"/>
</form>
```

```html:header.html(thymeleaf-2.1)
<form id="logoutForm" th:action="@{/logout}" method="POST">
  <input type="hidden" 
    th:name="${_csrf.parameterName}" 
    th:value="${_csrf.token}"/>
</form>
```