ログアウトの処理

エンドユーザーがログインできるアプリケーションでは、ログアウトもできる必要があります。

デフォルトでは、Spring Security は /logout エンドポイントを立ち上げるため、追加のコードは必要ありません。

このセクションの残りの部分では、考慮すべき多くの使用例について説明します。

Logout のアーキテクチャを理解する

spring-boot-starter-security 依存関係を含めるか、@EnableWebSecurity アノテーションを使用すると、Spring Security はログアウトサポートを追加し、デフォルトで GET /logout と POST /logout の両方に応答します。

GET /logout をリクエストすると、Spring Security はログアウト確認ページを表示します。ユーザーに価値のある二重チェックメカニズムを提供するだけでなく、必要な CSRF トークンを POST /logout に提供する簡単な方法も提供します。

CSRF の保護が構成で無効になっている場合、ログアウト確認ページはユーザーに表示されず、ログアウトが直接実行されることに注意してください。

アプリケーションでは、ログアウトを実行するために GET /logout を使用する必要はありません。必要な CSRF トークンがリクエスト内に存在する限り、アプリケーションは単純に POST /logout を実行してログアウトを誘導できます。

POST /logout をリクエストすると、一連の LogoutHandler (Javadoc) インスタンスを使用して次のデフォルト操作が実行されます。

完了すると、デフォルトの LogoutSuccessHandler (Javadoc) が実行され、/login?logout にリダイレクトされます。

ログアウト URI のカスタマイズ

LogoutFilter はフィルターチェーン内で AuthorizationFilter の前に現れるため、デフォルトでは /logout エンドポイントを明示的に認可する必要はありません。通常、到達可能にするために permitAll 構成が必要なのは、自分で作成したカスタムログアウトエンドポイントのみです。

例: Spring Security が一致する URI を単純に変更したい場合は、logout DSL で次の方法で変更できます。

カスタムログアウト URI
  • Java

  • Kotlin

  • XML

http
    .logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
    logout {
        logoutUrl = "/my/logout/uri"
    }
}
<logout logout-url="/my/logout/uri"/>

また、LogoutFilter を調整するだけなので、権限の変更は必要ありません。

ただし、独自のログアウト成功エンドポイント (または、まれに独自のログアウトエンドポイント ) を立ち上げる場合 (たとえば Spring MVC を使用する場合)、それを Spring Security で許可する必要があります。これは、Spring Security がリクエストを処理した後に Spring MVC がリクエストを処理するためです。

次のように authorizeHttpRequests または <intercept-url> を使用してこれを行うことができます。

カスタムログアウトエンドポイント
  • Java

  • Kotlin

  • XML

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my/success/endpoint").permitAll()
        // ...
    )
    .logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
http {
    authorizeHttpRequests {
        authorize("/my/success/endpoint", permitAll)
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
    }
}
<http>
    <filter-url pattern="/my/success/endpoint" access="permitAll"/>
    <logout logout-success-url="/my/success/endpoint"/>
</http>

この例では、完了したら /my/success/endpoint にリダイレクトするように LogoutFilter に指示します。また、AuthorizationFilter で /my/success/endpoint エンドポイントを明示的に認可します。

ただし、2 回指定すると面倒になる場合があります。Java 構成を使用している場合は、代わりに次のようにログアウト DSL で permitAll プロパティを設定できます。

カスタムログアウトエンドポイントの許可
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        // ...
    )
    .logout((logout) -> logout
        .logoutSuccessUrl("/my/success/endpoint")
        .permitAll()
    )
http
    authorizeHttpRequests {
        // ...
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
        permitAll = true
    }

これにより、すべてのログアウト URI が許可リストに追加されます。

クリーンアップアクションの追加

Java 構成を使用している場合は、次のように logout DSL で addLogoutHandler メソッドを呼び出すことで、独自のクリーンアップアクションを追加できます。

カスタムログアウトハンドラー
  • Java

  • Kotlin

CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout) -> logout.addLogoutHandler(cookies))
http {
    logout {
        addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
    }
}
LogoutHandler (Javadoc) インスタンスはクリーンアップを目的としているため、例外をスローしてはなりません。
LogoutHandler (Javadoc) は関数型インターフェースであるため、カスタムインターフェースをラムダとして提供できます。

一部のログアウトハンドラー構成は、logout DSL および <logout> 要素で直接公開されるほど一般的です。1 つの例はセッションの無効化の構成であり、もう 1 つは追加の Cookie を削除する必要があることです。

例: 上記のように CookieClearingLogoutHandler (Javadoc) を設定できます。

または、代わりに次のように適切な構成値を設定することもできます。

  • Java

  • Kotlin

  • XML

http
    .logout((logout) -> logout.deleteCookies("our-custom-cookie"))
http {
    logout {
        deleteCookies = "our-custom-cookie"
    }
}
<http>
    <logout delete-cookies="our-custom-cookie"/>
</http>
SecurityContextLogoutHandler (Javadoc) はセッションを無効にすることで JSESSIONID Cookie を削除するため、JSESSIONID Cookie が必要ないことを指定します。

Clear-Site-Data を使用してユーザーをログアウトする

Clear-Site-Data HTTP ヘッダーは、所有する Web サイトに属する Cookie、ストレージ、キャッシュをクリアする命令としてブラウザーがサポートするヘッダーです。これは、ログアウト時にセッション Cookie を含むすべてが確実にクリーンアップされる便利で安全な方法です。

次のように、ログアウト時に Clear-Site-Data ヘッダーを書き込むように Spring Security の設定を追加できます。

Clear-Site-Data の使用
  • Java

  • Kotlin

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

ClearSiteDataHeaderWriter コンストラクターに、消去したいもののリストを渡します。

上記の設定ではすべてのサイトデータが消去されますが、次のように Cookie のみを削除するように設定することもできます。

Clear-Site-Data を使用して Cookie をクリアする
  • Java

  • Kotlin

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.COOKIES))
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

ログアウト成功のカスタマイズ

ほとんどの場合は logoutSuccessUrl の使用で十分ですが、ログアウトの完了後に URL へのリダイレクトとは異なる操作が必要になる場合があります。LogoutSuccessHandler (Javadoc) は、ログアウト成功アクションをカスタマイズするための Spring Security コンポーネントです。

例: リダイレクトの代わりに、ステータスコードのみを返したい場合があります。この場合、次のように成功ハンドラーインスタンスを提供できます。

ログアウト成功時に HTTP ステータスコードを返すようにカスタマイズする
  • Java

  • Kotlin

  • XML

http
    .logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
http {
    logout {
        logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
    }
}
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
<http>
    <logout success-handler-ref="mySuccessHandlerBean"/>
</http>
LogoutSuccessHandler (Javadoc) は関数型インターフェースであるため、カスタムインターフェースをラムダとして提供できます。

カスタムログアウトエンドポイントの作成

提供されている logout DSL を使用してログアウトを構成することを強くお勧めします。理由の 1 つは、適切かつ完全なログアウトを保証するために必要な Spring Security コンポーネントを呼び出すのを忘れやすいためです。

実際、多くの場合、ログアウトを実行するための Spring MVC エンドポイントを作成するよりも、カスタム LogoutHandler を登録する方が簡単です。

ただし、次のようなカスタムログアウトエンドポイントが必要な状況に陥った場合は、次のようになります。

カスタムログアウトエンドポイント
  • Java

  • Kotlin

@PostMapping("/my/logout")
public String performLogout() {
    // .. perform logout
    return "redirect:/home";
}
@PostMapping("/my/logout")
fun performLogout(): String {
    // .. perform logout
    return "redirect:/home"
}

次に、安全で完全なログアウトを確保するために、そのエンドポイントで Spring Security の SecurityContextLogoutHandler (Javadoc) を呼び出す必要があります。少なくとも次のようなものが必要です。

カスタムログアウトエンドポイント
  • Java

  • Kotlin

SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
    // .. perform logout
    this.logoutHandler.doLogout(request, response, authentication);
    return "redirect:/home";
}
val logoutHandler = SecurityContextLogoutHandler()

@PostMapping("/my/logout")
fun performLogout(val authentication: Authentication, val request: HttpServletRequest, val response: HttpServletResponse): String {
    // .. perform logout
    this.logoutHandler.doLogout(request, response, authentication)
    return "redirect:/home"
}

これにより、必要に応じて SecurityContextHolderStrategy (Javadoc) SecurityContextRepository (Javadoc) がクリアされます。

また、エンドポイントを明示的に許可する必要があります。

SecurityContextLogoutHandler (Javadoc) の呼び出しに失敗するということは、SecurityContext が後続のリクエストで引き続き使用できる可能性があること、つまりユーザーが実際にはログアウトされていないことを意味します。

ログアウトのテスト

ログアウトを構成したら、Spring Security の MockMvc サポートを使用してテストできます。

その他のログアウト関連の参照