ログアウト処理は認証機能の重要な一部であり、適切に実装しないとセッションハイジャックやセキュリティトークンの漏洩といったリスクにつながります。本記事では、Spring Securityのlogout()設定オプション、SecurityContextHolderの仕組み、セッション・Cookie・トークンの無効化処理、そしてカスタムLogoutHandlerの実装方法を解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Java |
17以上 |
| Spring Boot |
3.4.x |
| Spring Security |
6.4.x |
| ビルドツール |
Maven または Gradle |
| IDE |
VS Code または IntelliJ IDEA |
事前に以下の知識があると理解がスムーズです。
- Spring Securityの基本概念(認証・認可の違い)
- SecurityFilterChainの設定方法
- HTTPセッションとCookieの基本
SecurityContextHolderの仕組み#
ログアウト処理を理解するには、まずSpring Securityが認証情報をどのように管理しているかを把握する必要があります。SecurityContextHolderは認証済みユーザーの情報を保持する中核コンポーネントです。
SecurityContextHolderのアーキテクチャ#
SecurityContextHolderは、SecurityContextを保持し、SecurityContextはAuthenticationオブジェクトを含みます。この階層構造によって認証情報が管理されます。
flowchart TB
A[SecurityContextHolder] --> B[SecurityContext]
B --> C[Authentication]
C --> D[Principal<br/>ユーザー情報]
C --> E[Credentials<br/>資格情報]
C --> F[Authorities<br/>権限]各コンポーネントの役割は以下のとおりです。
| コンポーネント |
役割 |
SecurityContextHolder |
SecurityContextの保存・取得を管理するコンテナ |
SecurityContext |
現在認証されているユーザーのAuthenticationを保持 |
Authentication |
ユーザーの識別情報、資格情報、権限を表現 |
SecurityContextHolderの保存戦略#
SecurityContextHolderはデフォルトでThreadLocalを使用して認証情報を保存します。これにより、同じスレッド内のどこからでも認証情報にアクセスできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
// 現在の認証情報を取得
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
}
|
SecurityContextHolderの保存戦略は以下の3種類があります。
| 戦略 |
説明 |
ユースケース |
MODE_THREADLOCAL |
スレッドごとに独立したコンテキスト(デフォルト) |
一般的なWebアプリケーション |
MODE_INHERITABLETHREADLOCAL |
子スレッドに親のコンテキストを継承 |
非同期処理で認証情報を引き継ぐ場合 |
MODE_GLOBAL |
JVM全体で1つのコンテキストを共有 |
スタンドアロンアプリケーション |
保存戦略の変更は、システムプロパティまたは静的メソッドで設定できます。
1
2
3
4
5
|
// システムプロパティで設定
System.setProperty("spring.security.strategy", "MODE_INHERITABLETHREADLOCAL");
// または静的メソッドで設定
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
|
ログアウト時のSecurityContext処理#
ログアウト時には、SecurityContextHolderから認証情報をクリアする必要があります。これを行わないと、同じスレッドを再利用した際に前のユーザーの認証情報が残ってしまう危険性があります。
sequenceDiagram
participant User as ユーザー
participant Filter as LogoutFilter
participant Handler as SecurityContextLogoutHandler
participant Holder as SecurityContextHolder
participant Repo as SecurityContextRepository
User->>Filter: POST /logout
Filter->>Handler: logout()
Handler->>Holder: clearContext()
Handler->>Repo: saveContext(emptyContext)
Handler-->>Filter: 完了
Filter-->>User: リダイレクト(/login?logout)Spring Securityのログアウトアーキテクチャ#
Spring Securityのログアウト機能は、LogoutFilterによって処理されます。LogoutFilterは複数のLogoutHandlerを順番に実行し、最後にLogoutSuccessHandlerを呼び出します。
ログアウト処理の流れ#
flowchart TB
A[POST /logout] --> B[LogoutFilter]
B --> C{URLがマッチ?}
C -->|No| D[次のFilterへ]
C -->|Yes| E[LogoutHandlers実行]
E --> F[SecurityContextLogoutHandler]
F --> G[CsrfLogoutHandler]
G --> H[CookieClearingLogoutHandler]
H --> I[LogoutSuccessHandler]
I --> J[リダイレクトまたはレスポンス]デフォルトのLogoutHandler#
Spring Security 6.xでは、以下のLogoutHandlerがデフォルトで登録されています。
| LogoutHandler |
役割 |
SecurityContextLogoutHandler |
セッションの無効化、SecurityContextHolderのクリア、SecurityContextRepositoryのクリア |
CsrfLogoutHandler |
保存されたCSRFトークンを削除 |
LogoutSuccessEventPublishingLogoutHandler |
LogoutSuccessEventを発行 |
Remember-Me認証を使用している場合は、追加でTokenRememberMeServicesまたはPersistentTokenRememberMeServicesが登録されます。
logout()の設定オプション#
SecurityFilterChainでlogout()を使用して、ログアウト処理をカスタマイズできます。
基本的なログアウト設定#
最もシンプルなログアウト設定は以下のとおりです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults());
return http.build();
}
}
|
Customizer.withDefaults()を使用した場合、以下のデフォルト動作が適用されます。
| 項目 |
デフォルト値 |
| ログアウトURL |
/logout(GETで確認ページ、POSTで実行) |
| ログアウト成功後のリダイレクト先 |
/login?logout |
| セッション無効化 |
有効 |
SecurityContextクリア |
有効 |
| CSRF保護 |
有効(POSTリクエストが必要) |
詳細なログアウト設定#
ログアウトURLやリダイレクト先をカスタマイズする場合は、以下のように設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/api/logout") // ログアウトを処理するURL
.logoutSuccessUrl("/login?logout") // ログアウト成功後のリダイレクト先
.invalidateHttpSession(true) // セッションを無効化
.clearAuthentication(true) // Authentication をクリア
.deleteCookies("JSESSIONID", "remember-me") // 指定したCookieを削除
.permitAll() // ログアウトURLへのアクセスを許可
);
return http.build();
}
|
各設定オプションの詳細は以下のとおりです。
| メソッド |
説明 |
デフォルト値 |
logoutUrl(String) |
ログアウトを処理するURL |
/logout |
logoutSuccessUrl(String) |
ログアウト成功後のリダイレクト先 |
/login?logout |
invalidateHttpSession(boolean) |
セッションを無効化するか |
true |
clearAuthentication(boolean) |
Authenticationをクリアするか |
true |
deleteCookies(String...) |
削除するCookie名を指定 |
なし |
permitAll() |
ログアウト関連URLへのアクセスを許可 |
- |
REST APIでのログアウト設定#
REST APIでは、リダイレクトではなくHTTPステータスコードを返すことが一般的です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.http.HttpStatus;
@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable())
.logout(logout -> logout
.logoutUrl("/api/logout")
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
);
return http.build();
}
|
この設定では、ログアウト成功時に200 OKのステータスコードのみを返します。
セッション・Cookie・トークンの無効化#
安全なログアウト処理には、セッション、Cookie、認証トークンを適切に無効化する必要があります。
セッションの無効化#
SecurityContextLogoutHandlerは、デフォルトでHTTPセッションを無効化します。この動作はinvalidateHttpSession(true)で制御されます。
1
2
3
4
5
6
7
8
9
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.invalidateHttpSession(true) // セッションを無効化(デフォルト: true)
);
return http.build();
}
|
セッション無効化時に実行される処理は以下のとおりです。
HttpSession.invalidate()の呼び出し
- セッションに紐づくすべての属性の削除
- セッションIDの無効化
Cookieの削除#
deleteCookies()メソッドで、ログアウト時に削除するCookieを指定できます。
1
2
3
4
5
6
7
8
9
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.deleteCookies("JSESSIONID", "remember-me", "custom-cookie")
);
return http.build();
}
|
内部的にはCookieClearingLogoutHandlerが使用されます。手動で追加することも可能です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
CookieClearingLogoutHandler cookies =
new CookieClearingLogoutHandler("custom-cookie-1", "custom-cookie-2");
http
.logout(logout -> logout
.addLogoutHandler(cookies)
);
return http.build();
}
|
Clear-Site-Dataヘッダーによるクリーンアップ#
モダンブラウザでは、Clear-Site-Dataヘッダーを使用してCookie、ストレージ、キャッシュを一括でクリアできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// すべてのサイトデータをクリア
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(
new ClearSiteDataHeaderWriter(Directive.ALL)
);
http
.logout(logout -> logout
.addLogoutHandler(clearSiteData)
);
return http.build();
}
|
Directiveで指定できる値は以下のとおりです。
| Directive |
クリア対象 |
CACHE |
ブラウザキャッシュ |
COOKIES |
Cookie |
STORAGE |
localStorage、sessionStorage、IndexedDBなど |
EXECUTION_CONTEXTS |
Service Workerなど |
ALL |
上記すべて |
Cookieのみをクリアする設定例です。
1
2
3
4
5
6
7
8
|
HeaderWriterLogoutHandler clearCookies = new HeaderWriterLogoutHandler(
new ClearSiteDataHeaderWriter(Directive.COOKIES)
);
http
.logout(logout -> logout
.addLogoutHandler(clearCookies)
);
|
Remember-Meトークンの無効化#
Remember-Me認証を使用している場合、ログアウト時にトークンも無効化する必要があります。Spring Securityでは、Remember-Meを設定すると自動的にログアウト時のトークン削除が行われます。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.rememberMe(remember -> remember
.key("uniqueAndSecret")
.tokenValiditySeconds(86400)
)
.logout(logout -> logout
.deleteCookies("remember-me") // Remember-Me Cookieを明示的に削除
);
return http.build();
}
|
カスタムLogoutHandlerの実装#
アプリケーション固有のログアウト処理が必要な場合、LogoutHandlerインターフェースを実装してカスタムハンドラーを作成できます。
LogoutHandlerインターフェース#
LogoutHandlerは関数型インターフェースであり、ラムダ式でも実装できます。
1
2
3
4
5
6
7
8
9
10
11
|
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
@FunctionalInterface
public interface LogoutHandler {
void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication);
}
|
ログ記録用カスタムLogoutHandler#
ログアウト時に監査ログを記録するカスタムハンドラーの実装例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
@Component
public class AuditLogoutHandler implements LogoutHandler {
private static final Logger logger = LoggerFactory.getLogger(AuditLogoutHandler.class);
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
if (authentication != null) {
String username = authentication.getName();
String ipAddress = getClientIpAddress(request);
String sessionId = request.getRequestedSessionId();
logger.info("User logout - username: {}, IP: {}, sessionId: {}",
username, ipAddress, sessionId);
}
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}
|
トークン無効化用カスタムLogoutHandler#
JWTのRefresh Tokenやカスタムトークンを無効化するハンドラーの例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
@Component
public class TokenInvalidationLogoutHandler implements LogoutHandler {
private final TokenRepository tokenRepository;
public TokenInvalidationLogoutHandler(TokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
}
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
// Authorizationヘッダーからトークンを取得
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// トークンをブラックリストに追加または無効化
tokenRepository.invalidateToken(token);
}
// ユーザーの全Refresh Tokenを無効化
if (authentication != null) {
String username = authentication.getName();
tokenRepository.invalidateAllUserTokens(username);
}
}
}
|
カスタムLogoutHandlerの登録#
カスタムLogoutHandlerはaddLogoutHandler()メソッドで登録します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final AuditLogoutHandler auditLogoutHandler;
private final TokenInvalidationLogoutHandler tokenInvalidationLogoutHandler;
public SecurityConfig(AuditLogoutHandler auditLogoutHandler,
TokenInvalidationLogoutHandler tokenInvalidationLogoutHandler) {
this.auditLogoutHandler = auditLogoutHandler;
this.tokenInvalidationLogoutHandler = tokenInvalidationLogoutHandler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutUrl("/logout")
.addLogoutHandler(auditLogoutHandler)
.addLogoutHandler(tokenInvalidationLogoutHandler)
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
);
return http.build();
}
}
|
LogoutHandlerの実行順序は、追加した順番に依存します。LogoutHandlerは例外をスローすべきではありません。
ラムダ式によるカスタムLogoutHandler#
シンプルな処理であれば、ラムダ式でインラインに記述できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.addLogoutHandler((request, response, authentication) -> {
if (authentication != null) {
System.out.println("Logging out user: " + authentication.getName());
}
})
);
return http.build();
}
|
カスタムLogoutSuccessHandlerの実装#
ログアウト成功後の処理をカスタマイズするには、LogoutSuccessHandlerを実装します。
LogoutSuccessHandlerインターフェース#
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import java.io.IOException;
public interface LogoutSuccessHandler {
void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException;
}
|
JSONレスポンスを返すLogoutSuccessHandler#
REST APIでJSONレスポンスを返す実装例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
public class JsonLogoutSuccessHandler implements LogoutSuccessHandler {
private final ObjectMapper objectMapper;
public JsonLogoutSuccessHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
Map<String, Object> responseBody = Map.of(
"status", "success",
"message", "ログアウトが完了しました",
"timestamp", System.currentTimeMillis()
);
objectMapper.writeValue(response.getOutputStream(), responseBody);
}
}
|
LogoutSuccessHandlerの登録#
1
2
3
4
5
6
7
8
9
10
11
|
@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.logout(logout -> logout
.logoutUrl("/api/logout")
.logoutSuccessHandler(jsonLogoutSuccessHandler)
);
return http.build();
}
|
カスタムログアウトエンドポイントの作成#
LogoutFilterを使わずに、Spring MVCのコントローラーでログアウト処理を行うことも可能です。ただし、この場合はSecurityContextLogoutHandlerを明示的に呼び出す必要があります。
コントローラーでのログアウト実装#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogoutController {
private final SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
@PostMapping("/custom/logout")
public Map<String, String> performLogout(HttpServletRequest request,
HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
// SecurityContextLogoutHandlerでログアウト処理を実行
logoutHandler.logout(request, response, authentication);
}
return Map.of("message", "ログアウトしました");
}
}
|
SecurityContextLogoutHandlerを呼び出さないと、SecurityContextが残ったままになり、実質的にログアウトされない状態になるため、注意が必要です。
SecurityFilterChainでカスタムエンドポイントを許可#
カスタムログアウトエンドポイントを使用する場合、AuthorizationFilterで許可する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/custom/logout").permitAll()
.anyRequest().authenticated()
)
.logout(logout -> logout
.disable() // デフォルトのLogoutFilterを無効化
);
return http.build();
}
|
ログアウト処理のテスト#
Spring Security Test を使用して、ログアウト処理をテストできます。
MockMvcによるログアウトテスト#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
class LogoutIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(username = "testuser")
void logout_shouldInvalidateSessionAndRedirect() throws Exception {
mockMvc.perform(post("/logout")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login?logout"));
}
@Test
@WithMockUser(username = "testuser")
void logout_shouldClearSecurityContext() throws Exception {
// ログアウト実行
mockMvc.perform(post("/logout")
.with(csrf()))
.andExpect(status().is3xxRedirection());
// ログアウト後、保護されたリソースにアクセスできないことを確認
mockMvc.perform(post("/protected-resource"))
.andExpect(status().isUnauthorized());
}
}
|
REST APIのログアウトテスト#
1
2
3
4
5
6
7
8
|
@Test
@WithMockUser(username = "apiuser")
void apiLogout_shouldReturnOkStatus() throws Exception {
mockMvc.perform(post("/api/logout")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("success"));
}
|
まとめ#
Spring Securityのログアウト処理とSecurityContextの管理について解説しました。要点をまとめます。
| 項目 |
内容 |
SecurityContextHolder |
認証情報をThreadLocalで管理し、ログアウト時にクリアが必要 |
| デフォルトのログアウト処理 |
セッション無効化、SecurityContextクリア、CSRFトークン削除を自動実行 |
logout()設定 |
logoutUrl、logoutSuccessUrl、deleteCookiesなどでカスタマイズ可能 |
LogoutHandler |
クリーンアップ処理を追加するためのインターフェース |
LogoutSuccessHandler |
ログアウト成功後の処理をカスタマイズ |
Clear-Site-Data |
モダンブラウザでCookie、ストレージ、キャッシュを一括クリア |
安全なログアウト処理を実装するには、セッション、Cookie、認証トークンのすべてを適切に無効化し、SecurityContextを確実にクリアすることが重要です。
参考リンク#