9.4. セッション管理


9.4.1. Overview

本節では、「Webアプリケーションでセッションを扱う際に必要となるセキュリティ対策」及び「Spring Securityが提供しているセッション関連の機能」について説明する。


9.4.1.1. セッション利用時のセキュリティ対策

Webアプリケーションでセッションを扱う場合、一般的には以下の攻撃に対して対策が必要となる。

対策

説明

セッションハイジャック攻撃
通信の盗聴、規則性からの類推、クロスサイトスクリプティングなどを駆使してセッションIDを盗みとり、盗みとったセッションIDをつかっているユーザーになりすましてシステムを利用する攻撃。
セッション固定攻撃
攻撃者が事前に払い出したセッションIDを他人に使わせてシステムにログインさせ、攻撃者がログインしたユーザーになりすましてシステムを利用する攻撃。

9.4.1.1.1. セッションハイジャック攻撃への対策

セッションハイジャック攻撃への対策は、セッションIDが盗み取られないようにするしかない。
いったん盗み取られてしまうと、アプリケーションサーバは正規のユーザーからのリクエストなのか、攻撃者からのリクエストなのかを判断することができない。

このようなセッションハイジャック攻撃からアプリケーションを守るためには、以下のような対策が必要である。

セッションハイジャック攻撃への対策

対策

説明

推測困難なセッションIDの生成する
連番など推測できる値をセッションIDに使用せず、推測が困難な(セキュアな)ランダム値を使用する。
基本的にはアプリケーションサーバが提供するセッションIDの生成機構を利用すればよい。
HTTPSを使って通信を暗号化する
盗まれると困る情報をやりとりする通信は、HTTPSプロトコルを使って暗号化する。
通信の盗聴はフリーのソフトなどを使って簡単に行うことができため、盗聴されても解読されないように暗号化しておくことが重要である。
セッションIDはCookieを使って連携する
クライアントとサーバーとの間でセッションIDを連携する際は、Cookieを使って連携するように設定し、URL Rewriting機能を無効化する。
CookieのHttpOnly属性を指定する
CookieのHttpOnly属性を指定すると、JavaScriptからCookieにアクセスすることができなくため、クロスサイトスクリプティングを使ってセッションIDを盗むことができなくなる。
CookieにSecure属性を指定する
CookieにSecure属性を指定すると、HTTPS通信の時だけCookieをサーバーに送信するため、誤ってHTTP通信を使ってしまった時にセッションIDが盗み取られるリスクを減らすことができる。

Note

URL Rewriting

URL Rewritingは、Cookieを使用できないクライアントとセッションを維持するための仕組みである。

具体的には、URLのリクエストパラメータの中にセッションIDを含めることでクライアントとサーバーの間でセッションIDを連携する。

  • URL Rewritingが行われたURL例

    http://localhost:8080/;jsessionid=7E6EDE4D3317FC5F14FD912BEAC96646
    

jsessionid=7E6EDE4D3317FC5F14FD912BEAC96646の部分がURL RewritingされたセッションIDになる。ServletのAPI仕様では、以下のメソッドを呼び出すとURL Rewritingが行われる可能性があり、JSTLやSpringが提供しているJSPタグライブラリの中でもこれらのメソッドを呼び出している。

  • HttpServletResponse#encodeURL(String)

  • HttpServletResponse#encodeRedirectURL(String)

このうち、ThymeleafのリンクURL式@{}encodeURLメソッドを呼び出している。

URL Rewritingが行われるとURL内にセッションIDが露出してしまうため、セッションIDを盗まれるリスクが高くなる。そのため、Cookieを使うことができるクライアントのみをサポートする場合は、サーブレットコンテナのURL Rewriting機能を無効化することを推奨する。

なお、Spring Security 5.0.1, 4.2.4, 4.1.5以降では、URLにセミコロンが含まれる場合、無効なリクエストと判断される。そのため、デフォルトの設定ではURL Rewritingによるセッションの共有は行えない。

セミコロンが含まれるURLを許可するように変更することも可能であるが、認証認可のバイパスやReflected File Download(RFD)攻撃に対する脆弱性が発生する可能性があるため、推奨しない。

詳細は、StrictHttpFirewall#setAllowSemicolonを参照されたい。


9.4.1.1.2. セッション固定攻撃への対策

セッション固定攻撃からアプリケーションを守るためには、以下のような対策が必要になる。

セッション固定攻撃への対策

対策

説明

URL Rewriting機能を無効化する
URL Rewriting機能を無効化すると、攻撃者が事前に払い出したセッションIDが使われず、新たにセッションが開始される。
ログイン後にセッションIDを変更する
ログイン後にセッションIDを変更することで、攻撃者が事前に払い出したセッションIDが使用できなくなる。

9.4.1.2. Spring Securityが提供するセッション管理機能

Spring Securityでは、セッションについて、主に以下の機能が提供されている。

セッションに関する提供機能

機能

説明

セキュリティ対策
セッションハイジャック攻撃等のセッションIDを使用した攻撃への対策機能。
ライフサイクル制御
セッションの生成~破棄までのライフサイクルを制御する機能。
タイムアウト制御
タイムアウトにより、セッションを破棄する機能。
多重ログイン制御
同一ユーザーによる多重ログイン時のセッションを制御する機能。

9.4.2. How to use

9.4.2.1. セッションハイジャック攻撃への対策

ここではURL Rewriting機能を無効化し、Cookieを使用してセッションIDを連携する方法を説明する。


9.4.2.1.1. Spring SecurityによるURL Rewriting機能の無効化

Spring SecurityはURL Rewritingを無効化するための仕組みを提供しており、この機能はデフォルトで適用されている。
Cookieを使えないクライアントをサポートする必要がある場合は、URL Rewritingを許可するようにBean定義する。
  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement.enableSessionUrlRewriting(true)); // (1)
    // omitted
    return http.build();
}

項番

説明

(1)
Spring Securityのデフォルトでは、enableSessionUrlRewritingの値はfalseであるため、URL Rewritingは行われない。
URL Rewritingを有効にする際は、HttpSecurity#sessionManagementSessionManagementConfigurer#enableSessionUrlRewritingtrueを設定する。

9.4.2.1.2. サーブレットコンテナによるURL Rewriting機能の無効化

Servletの標準仕様の仕組みを使ってセッションをセキュアに扱うことが可能である。

  • web.xmlの定義例

<session-config>
    <cookie-config>
        <http-only>true</http-only> <!-- (1)  -->
    </cookie-config>
    <tracking-mode>COOKIE</tracking-mode> <!-- (2) -->
</session-config>

項番

説明

(1)
CookieにHttpOnly属性を付与する場合は、<http-only>要素にtrueを指定する。
使用するアプリケーションサーバによっては、デフォルト値がtrueになっている。
(3)
URL Rewriting機能を無効化する場合は、<tracking-mode>要素にCOOKIEを指定する。
上記の定義例からは省略しているが、<cookie-config><secure>true</secure>を追加することで、 CookieにSecure属性を付与することができる。
ただし、cookieのsecure化は、web.xmlで指定するのではなく、クライアントとHTTPS通信を行うミドルウェア(SSLアクセラレータやWebサーバーなど)で付与する方法を検討されたい。
実際のシステム開発の現場において、ローカルの開発環境でHTTPSを使うケースはほとんどない。
また、本番環境においても、HTTPSを使うのはSSLアクセラレータやWebサーバーとの通信までで、アプリケーションサーバへの通信はHTTPで行うケースも少なくない。
このような環境下でSecure属性の指定をweb.xmlで行ってしまうと、実行環境毎にweb.xmlweb-fragment.xmlを用意することになり、ファイルの管理が煩雑になるため推奨されない。

9.4.2.2. セッション管理機能の適用

Spring Securityのセッション管理機能を適用する方法を説明する。
Spring Securityのセッション管理機能の処理を使用する場合は、以下のようなbean定義を行う。
  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(Customizer.withDefaults()); // (1)
    // omitted
    return http.build();
}

項番

説明

(1)
HttpSecurity#sessionManagementを指定する。
HttpSecurity#sessionManagementを指定すると、セッション管理機能が適用される。

9.4.2.3. セッション固定攻撃への対策

Spring Securityは、セッション固定攻撃対策として、ログイン成功時にセッションIDを変更するためのオプションを4つ用意している。

セッション固定攻撃への対策のオプション

オプション

説明

changeSessionId
Servlet 3.1で追加されたHttpServletRequest#changeSessionId()を使用してセッションIDを変更する。
(これはServlet 3.1以上のコンテナ上でのデフォルトの動作である)
migrateSession
ログイン前に使用していたセッションを破棄し、新たにセッションを作成する。
このオプションを使用すると、ログイン前にセッションに格納されていたオブジェクトは新しいセッションに引き継がれる。
(Servlet 3.0以下のコンテナ上でのデフォルトの動作である)
newSession
このオプションはmigrateSessionと同じ方法でセッションIDを変更するが、ログイン前に格納されていたオブジェクトは新しいセッションに引き継がれない。
none
Spring Securityは、セッションIDを変更しない。

デフォルトの動作を変更したい場合は、以下のようなbean定義を行う。

  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .sessionFixation(fixation -> fixation.newSession())); // (1)
    // omitted
    return http.build();
}

項番

説明

(1)
SessionManagementConfigurer#sessionFixationSessionFixationConfigurerにセッション固定攻撃の対策方法を指定する。

9.4.2.4. セッションのライフサイクル制御

Spring Securityは、リクエストを跨いで認証情報などのオブジェクトを共有するための手段としてHTTPセッションを使用しており、Spring Securityの処理の中でセッションのライフサイクル(セッションの作成と破棄)を制御している。

Note

セッション情報の格納先

Spring Securityが用意しているデフォルト実装ではHTTPセッションを使用するが、HTTPセッション以外(データベースやキーバリューストアなど)にオブジェクトを格納することも可能なアーキテクチャになっている。


9.4.2.4.1. セッションの作成

Spring Securityの処理の中でどのような方針でセッションを作成して利用するかは、以下のオプションから選択することができる。

セッションの作成方針

オプション

説明

always
セッションが存在しない場合は、無条件に新たなセッションを生成する。
このオプションを指定すると、Spring Securityの処理でセッションを使わないケースでもセッションが作成される。
ifRequired
セッションが存在しない場合は、セッションにオブジェクトを格納するタイミングで新たなセッションを作成して利用する。(デフォルトの動作)
never
セッションが存在しない場合は、セッションの生成及び利用は行わない。
ただし、既にセッションが存在している場合はセッションを利用する。
stateless
セッションの有無に関係なく、セッションの生成及び利用は行わない。

デフォルトの振る舞いを変更したい場合は、以下のようなbean定義を行う。

  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // (1)
    // omitted

    return http.build();
}

項番

説明

(1)
SessionManagementConfigurer#sessionCreationPolicyに、変更したいセッションの作成方針を指定する。

9.4.2.4.2. セッションの破棄

Spring Securityは、以下のタイミングでセッションを破棄する。

  • ログアウト処理が実行されたタイミング

  • 認証処理が成功したタイミング (セッション固定攻撃対策としてmigrateSession又はnewSessionが適用されるとセッションが破棄される)


9.4.2.5. セッションタイムアウトの制御

セッションにオブジェクトを格納する場合、適切なセッションタイムアウト値を指定して、一定時間操作がないユーザーとのセッションを自動で破棄するようにするのが一般的である。


9.4.2.5.1. セッションタイムアウトの指定

セッションタイムアウトは、サーブレットコンテナに対して指定する。
アプリケーションサーバーによっては、サーバー独自の指定方法を用意しているケースもあるが、ここでは、Servlet標準仕様で定められた指定方法を説明する。
  • web.xmlの定義例

<session-config>
    <session-timeout>60</session-timeout> <!-- (1) -->
    <!-- ommited -->
</session-config>

項番

説明

(1)
<session-timeout>要素に適切なタイムアウト値(分単位)を指定する。
タイムアウト値を指定しない場合は、サーブレットコンテナが用意しているデフォルト値が適用される。
また、0以下の値を指定するとサーブレットコンテナのセッションタイム機能が無効化される。

9.4.2.5.2. 無効なセッションを使ったリクエストの検知

Spring Securityは、無効なセッションを使ったリクエストを検知する機能を提供している。
無効なセッションとして扱われるリクエストの大部分は、セッションタイムアウト後のリクエストである。
デフォルトではこの機能は無効になっているが、以下のようなbean定義を行うことで有効化することができる。
  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .invalidSessionUrl("/error/invalidSession")); // (1)
    // omitted
    return http.build();
}

項番

説明

(1)
SessionManagementConfigurer#invalidSessionUrlに、無効なセッションを使ったリクエストを検知した際のリダイレクト先のパスを指定する。

9.4.2.5.3. 除外パスの指定

無効なセッションを使ったリクエストを検知する機能を有効にすると、Spring Securityのサーブレットフィルタを通過するすべてのリクエストに対してチェックが行われる。
そのため、セッションが無効な状態でアクセスしても問題がないページにアクセスした場合もチェックが行われる。
この動作を変更したい場合は、チェック対象から除外したいパスに対して個別にbean定義を行うことで実現することが可能である。
例として、トップページを開くためのパス(”/“)を除外パスに指定したい場合は、以下のようなbean定義を行う。
  • SpringSecurityConfig.javaの定義例

// (1)
@Order(1)
@Bean
public SecurityFilterChain filterChainTopPage(HttpSecurity http) throws Exception {
    http.securityMatcher(new AntPathRequestMatcher("/"));
    // omitted
    http.sessionManagement(Customizer.withDefaults());
    // omitted
    return http.build();
}

// (2)
@Order(2)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .invalidSessionUrl("/error/invalidSession"));
    // omitted
    return http.build();
}

項番

説明

(1)
トップページを開くためのパス(”/“)に適用するSecurityFilterChainを作成するためのSecurityFilterChainを新たに追加する。
(2)
個別定義していないパスに適用するSecurityFilterChainを定義する。
この定義は、(1)の定義より下に定義すること。上記例ではさらに@Orderにより読み込み順を指定している。
これはSecurityFilterChainの定義順番がSecurityFilterChainの優先順位となるためである。

9.4.2.6. 多重ログインの制御

Spring Securityは、同じユーザー名(ログインID)を使った多重ログインを制御する機能を提供している。
デフォルトではこの機能は無効になっているが、セッションのライフサイクル検知の有効化を行うことで有効化することができる。

Warning

多重ログイン制御における制約

Spring Securityが提供しているデフォルト実装では、ユーザー毎のセッション情報をアプリケーションサーバーのメモリ内で管理しているため、以下の2つの制約がある。

ひとつめの制約として、複数のアプリケーションサーバーを同時に起動するシステムでは、デフォルト実装を利用することができないことが挙げられる。複数のアプリケーションサーバーを同時に使用する場合は、ユーザー毎のセッション情報をデータベースやキーバリューストア(キャッシュサーバー)などの共有領域で管理する実装クラスの作成が必要になる。

ふたつめの制約は、アプリケーションサーバーを停止または再起動時した際に、セッション情報が復元されると、正常動作しない可能性があるという点である。使用するアプリケーションサーバーによっては、停止または再起動時のセッション状態を復元する機能をもっているため、実際のセッション状態とSpring Securityが管理しているセッション情報に不整合が生じることになる。

このような不整合が生まれる可能性がある場合は、以下のいずれかの対応が必要になる。

  • アプリケーションサーバ側のセッション状態が復元されないようにする。

  • Spring Security側のセッション情報を復元する仕組みを実装する。

  • HTTPセッション以外(データベースやキーバリューストアなど)にオブジェクトを格納する。

本節では、Spring Securityのデフォルト実装を使用する方法を紹介する。
Spring Securityが用意しているデフォルト実装ではHTTPセッションを使用するが、HTTPセッション以外(データベースやキーバリューストアなど)にオブジェクトを格納することも可能なアーキテクチャになっている。
ただし、ここで紹介する方法は上記Warningの制約が残っている実装方法であるため、適用する際は注意されたい。

9.4.2.6.1. セッションのライフサイクル検知の有効化

多重ログインを制御する機能は、セッションのライフサイクル(セッションの生成と破棄)を検知する仕組みを利用してユーザー毎のセッション状態を管理している。
このため、多重ログインの制御機能を使用する際は、Spring Securityから提供されているHttpSessionEventPublisherクラスをサーブレットコンテナに登録する必要がある。
  • web.xmlの定義例

<listener>
    <!-- (1) -->
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

項番

説明

(1)
サーブレットリスナとしてHttpSessionEventPublisherを登録する。

9.4.2.6.2. 多重ログインの禁止(先勝ち)

同じユーザー名(ログインID)を使って既にログインしているユーザーがいる場合に、認証エラーを発生させて多重ログインを防ぐ場合は、以下のようなbean定義を行う。

  • bean定義ファイルの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .sessionConcurrency(sessionConcurrency -> sessionConcurrency
                    .maximumSessions(2) // (1)
                    .maxSessionsPreventsLogin(true))); // (2)
      // omitted
    return http.build();
}

項番

説明

(1)
ConcurrencyControlConfigurer#maximumSessionsに、同時にログインを許可するセッション数を指定する。
多重ログインを防ぎたい場合は”1“を指定する。
(2)
ConcurrencyControlConfigurer#maxSessionsPreventsLoginに、同時にログインできるセッション数を超えた時の動作を指定する。
既にログインしているユーザーを有効なユーザーとして扱う場合は、trueを指定する。(デフォルトfalse)

9.4.2.6.3. 多重ログインの禁止(後勝ち)

同じユーザー名(ログインID)を使って既にログインしているユーザーがいる場合に、既にログインしているユーザーを無効化することで多重ログインを防ぐ場合は、以下のようなbean定義を行う。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.sessionManagement(sessionManagement -> sessionManagement
            .sessionConcurrency(sessionConcurrency -> sessionConcurrency
                    .maximumSessions(1)
                    .maxSessionsPreventsLogin(false) // (1)
                    .expiredUrl("/error/expire"))); // (2)
      // omitted
    return http.build();
}

項番

説明

(1)
ConcurrencyControlConfigurer#maxSessionsPreventsLoginに、同時にログインできるセッション数を超えた時の動作を指定する。
新たにログインしたユーザーを有効なユーザーとして扱う場合は、falseを指定する。(デフォルトfalse)
(2)
ConcurrencyControlConfigurer#expiredUrlに、無効化されたユーザーからのリクエストを検知した際のリダイレクト先のパスを指定する。