最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

セッション管理

先行したセッションの作成を強制する

時には、先行してセッションを作成することが価値がある場合があります。これは、次を使用して構成できる ForceEagerSessionCreationFilter (Javadoc) を使用して実行できます。

  • Java

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
        );
    return http.build();
}
<http create-session="ALWAYS">

</http>

タイムアウトの検出

Spring Security を構成して、無効なセッション ID の送信を検出し、ユーザーを適切な URL にリダイレクトできます。これは、session-management 要素によって実現されます。

  • Java

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .invalidSessionUrl("/invalidSession.htm")
        );
    return http.build();
}
<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>

このメカニズムを使用してセッションタイムアウトを検出した場合、ユーザーがログアウトしてからブラウザーを閉じずに再度ログインすると、誤ってエラーが報告される可能性があることに注意してください。これは、セッションを無効にしてもセッション Cookie がクリアされず、ユーザーがログアウトしても再送信されるためです。ログアウトハンドラーで次の構文を使用するなどして、ログアウト時に JSESSIONID Cookie を明示的に削除できる場合があります。

  • Java

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .logout(logout -> logout
            .deleteCookies("JSESSIONID")
        );
    return http.build();
}
<http>
<logout delete-cookies="JSESSIONID" />
</http>

残念ながら、これはすべてのサーブレットコンテナーで動作することを保証できないため、自分の環境でテストする必要があります。

アプリケーションをプロキシの背後で実行している場合は、プロキシサーバーを構成することでセッションクッキーを削除できる場合もあります。例: Apache HTTPD の mod_headers を使用すると、次のディレクティブは、ログアウトリクエストへのレスポンスで期限切れにすることで JSESSIONID クッキーを削除します (アプリケーションがパス /tutorial にデプロイされていると仮定)。

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>

同時セッション制御

1 人のユーザーがアプリケーションにログインする機能に制約を課したい場合、Spring Security は、次の簡単な追加で、これをすぐにサポートします。まず、セッションライフサイクルイベントについて Spring Security を最新の状態に保つために、構成に次のリスナーを追加する必要があります。

  • Java

  • XML

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}
<listener>
<listener-class>
	org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

次に、アプリケーションコンテキストに次の行を追加します。

  • Java

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .maximumSessions(1)
        );
    return http.build();
}
<http>
...
<session-management>
	<concurrency-control max-sessions="1" />
</session-management>
</http>

これにより、ユーザーが複数回ログインするのを防ぐことができます。2 回目のログインでは、最初のログインが無効になります。多くの場合、2 回目のログインを禁止することをお勧めします。その場合

  • Java

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
        );
    return http.build();
}
<http>
<session-management>
	<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

2 回目のログインは拒否されます。「拒否」とは、フォームベースのログインが使用されている場合、ユーザーが authentication-failure-url に送信されることを意味します。2 番目の認証が "remember-me" などの別の非対話型メカニズムを介して行われる場合、"unauthorized" (401) エラーがクライアントに送信されます。代わりにエラーページを使用する場合は、session-authentication-error-url 属性を session-management 要素に追加できます。

フォームベースのログインにカスタマイズされた認証フィルターを使用している場合、同時セッション制御サポートを明示的に構成する必要があります。詳細については、セッション管理の章を参照してください。

セッション固定攻撃保護

セッション固定 [Wikipedia] 攻撃は、悪意のある攻撃者がサイトにアクセスしてセッションを作成し、別のユーザーに同じセッションでログインするように誘導することができる潜在的なリスクです(たとえば、パラメーターとしてセッション識別子を含むリンクを送信することなど)。Spring Security は、ユーザーがログインしたときに新しいセッションを作成するか、セッション ID を変更することで、自動的にこの問題から保護します。この保護を必要としない場合、または他の要件と競合する場合は、<session-management> の session-fixation-protection 属性を使用して動作を制御することができます(4 つのオプションがあります)。

  • none - 何もしないでください。元のセッションは保持されます。

  • newSession - 既存のセッションデータをコピーせずに、新しい「クリーン」セッションを作成します(Spring セキュリティ関連の属性は引き続きコピーされます)。

  • migrateSession - 新しいセッションを作成し、既存のすべてのセッション属性を新しいセッションにコピーします。これは、Servlet 3.0 以前のコンテナーのデフォルトです。

  • changeSessionId - 新しいセッションを作成しないでください。代わりに、サーブレットコンテナー(HttpServletRequest#changeSessionId())によって提供されるセッション固定保護を使用します。このオプションは、Servlet 3.1(Java EE 7)以降のコンテナーでのみ使用可能です。古いコンテナーで指定すると、例外が発生します。これは、Servlet 3.1 以降のコンテナーのデフォルトです。

セッション固定保護が発生すると、SessionFixationProtectionEvent がアプリケーションコンテキストで公開されます。changeSessionId を使用する場合、この保護により javax.servlet.http.HttpSessionIdListener も通知されるため、コードが両方のイベントをリッスンする場合は注意が必要です。詳細については、セッション管理の章を参照してください。

SessionManagementFilter

SessionManagementFilter は、SecurityContextRepository のコンテンツを SecurityContextHolder の現在のコンテンツと照合して、通常、事前認証や remember-me [ 1 ] などの非対話型認証メカニズムによって現在のリクエスト中にユーザーが認証されたかどうかを判断します。リポジトリにセキュリティコンテキストが含まれている場合、フィルターは何もしません。存在せず、スレッドローカル SecurityContext に(非匿名の) Authentication オブジェクトが含まれている場合、フィルターは、スタック内の前のフィルターによって認証されたと見なします。次に、構成された SessionAuthenticationStrategy を呼び出します。

ユーザーが現在認証されていない場合、フィルターは無効なセッション ID がリクエストされているかどうかを確認し(タイムアウトなどが原因)、設定されている InvalidSessionStrategy が設定されている場合はそれを呼び出します。最も一般的な動作は、固定 URL にリダイレクトすることであり、これは標準実装 SimpleRedirectInvalidSessionStrategy にカプセル化されています。後者は、前述のように、名前空間を介して無効なセッション URL を構成するときにも使用されます。

SessionAuthenticationStrategy

SessionAuthenticationStrategy は SessionManagementFilter と AbstractAuthenticationProcessingFilter の両方で使用されるため、たとえば、カスタマイズされたフォームログインクラスを使用している場合は、これらの両方にそれを挿入する必要があります。この場合、名前空間とカスタム Bean を組み合わせた一般的な構成は次のようになります。

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
	<beans:property name="sessionAuthenticationStrategy" ref="sas" />
	...
</beans:bean>

<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />

Spring セッションスコープ Bean を含む HttpSessionBindingListener を実装するセッションに Bean を格納している場合、デフォルトの SessionFixationProtectionStrategy を使用すると問題が発生する可能性があることに注意してください。詳細については、このクラスの Javadoc を参照してください。

並行性制御

Spring Security は、プリンシパルが指定された回数を超えて同じアプリケーションに対して同時に認証するのを防ぐことができます。多くの ISV はこれを利用してライセンスを強制しますが、ネットワーク管理者がこの機能を好むのは、ユーザーがログイン名を共有できないようにするためです。たとえば、ユーザー "Batman" が 2 つの異なるセッションから Web アプリケーションにログオンするのを停止できます。以前のログインを期限切れにするか、再度ログインしようとしたときにエラーを報告して、2 回目のログインを防ぐことができます。2 番目の方法を使用している場合、明示的にログアウトしていないユーザー(たとえば、ブラウザーを閉じたばかりのユーザー)は、元のセッションが期限切れになるまで再度ログインできないことに注意してください。

同時実行制御はネームスペースでサポートされているため、最も単純な構成については、以前のネームスペースの章を確認してください。ただし、場合によってはカスタマイズする必要があります。

実装では、ConcurrentSessionControlAuthenticationStrategy と呼ばれる SessionAuthenticationStrategy の特殊バージョンを使用します。

以前は、同時認証チェックは ProviderManager によって行われ、ConcurrentSessionController で注入できました。後者は、ユーザーが許可されたセッション数を超えようとしているかどうかをチェックします。ただし、このアプローチでは、HTTP セッションを事前に作成する必要があり、望ましくありません。Spring Security 3 では、ユーザーは最初に AuthenticationManager によって認証され、認証に成功すると、セッションが作成され、別のセッションを開くことが許可されているかどうかのチェックが行われます。

同時セッションのサポートを使用するには、以下を web.xml に追加する必要があります。

<listener>
	<listener-class>
	org.springframework.security.web.session.HttpSessionEventPublisher
	</listener-class>
</listener>

さらに、ConcurrentSessionFilter を FilterChainProxy に追加する必要があります。ConcurrentSessionFilter には、2 つのコンストラクター引数 sessionRegistry が必要です。これは一般に SessionRegistryImpl のインスタンスを指し、sessionInformationExpiredStrategy はセッションの有効期限が切れたときに適用する戦略を定義します。ネームスペースを使用して FilterChainProxy およびその他のデフォルト Bean を作成する構成は、次のようになります。

<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />

<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>

<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
	<beans:list>
	<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
		<beans:constructor-arg ref="sessionRegistry"/>
		<beans:property name="maximumSessions" value="1" />
		<beans:property name="exceptionIfMaximumExceeded" value="true" />
	</beans:bean>
	<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
	</beans:bean>
	<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
		<beans:constructor-arg ref="sessionRegistry"/>
	</beans:bean>
	</beans:list>
</beans:constructor-arg>
</beans:bean>

<beans:bean id="sessionRegistry"
	class="org.springframework.security.core.session.SessionRegistryImpl" />

web.xml にリスナーを追加すると、HttpSession が開始または終了するたびに ApplicationEvent が Spring ApplicationContext に公開されます。これは、セッションが終了したときに SessionRegistryImpl に通知されるため、重要です。これがないと、ユーザーが別のセッションからログアウトしたりタイムアウトしたりした場合でも、セッションの許容量を超えると、ユーザーは再びログインできなくなります。

現在認証されているユーザーとそのセッションについて SessionRegistry を照会する

名前空間またはプレーン Bean を使用して同時実行制御を設定すると、アプリケーション内で直接使用できる SessionRegistry への参照が提供されるという便利な副作用があります。ユーザーが持つ可能性のあるセッションについては、インフラストラクチャをセットアップする価値があるかもしれません。maximumSession プロパティを -1 に設定して、無制限のセッションを許可できます。名前空間を使用している場合は、session-registry-alias 属性を使用して内部で作成された SessionRegistry のエイリアスを設定し、独自の Bean に注入できる参照を提供できます。

getAllPrincipals() メソッドは、現在認証されているユーザーのリストを提供します。ユーザーのセッションをリストするには、getAllSessions(Object principal, boolean includeExpiredSessions) メソッドを呼び出して、SessionInformation オブジェクトのリストを返します。SessionInformation インスタンスで expireNow() を呼び出すことにより、ユーザーのセッションを期限切れにすることもできます。ユーザーがアプリケーションに戻ると、ユーザーは続行できなくなります。たとえば、これらのメソッドは管理アプリケーションで役立ちます。詳細については、Javadoc を参照してください。


1. 認証後にフィルターが呼び出されないため、認証後にリダイレクトを実行するメカニズム(フォームログインなど)による認証は SessionManagementFilter によって検出されません。これらの場合、セッション管理機能を個別に処理する必要があります。