アーキテクチャー

このセクションでは、サーブレットベースのアプリケーション内の Spring Security の高レベルアーキテクチャについて説明します。リファレンスの認証認可エクスプロイトに対する保護セクション内のこの高レベルの理解に基づいています。

フィルターのレビュー

Spring Security のサーブレットサポートはサーブレットフィルターに基づいているため、一般的に最初にフィルターのロールを確認すると便利です。次のイメージは、単一の HTTP リクエストのハンドラーの一般的な階層化を示しています。

filterchain
図 1: FilterChain

クライアントはアプリケーションにリクエストを送信し、コンテナーは FilterChain を作成します。これには、リクエスト URI のパスに基づいて、Filter インスタンスと HttpServletRequest を処理する必要がある Servlet が含まれます。Spring MVC アプリケーションでは、Servlet は DispatcherServlet のインスタンスです。最大で、1 つの Servlet が 1 つの HttpServletRequest および HttpServletResponse を処理できます。ただし、複数の Filter を使用して次のことができます。

  • ダウンストリーム Filter インスタンスまたは Servlet が呼び出されないようにします。この場合、Filter は通常 HttpServletResponse を書き込みます。

  • ダウンストリーム Filter インスタンスおよび Servlet によって使用される HttpServletRequest または HttpServletResponse を変更します。

Filter のパワーは、それに渡される FilterChain から得られます。

FilterChain の使用例
  • Java

  • Kotlin

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // do something before the rest of the application
    chain.doFilter(request, response) // invoke the rest of the application
    // do something after the rest of the application
}

Filter はダウンストリームの Filter インスタンスと Servlet にのみ影響を与えるため、各 Filter が呼び出される順序は非常に重要です。

DelegatingFilterProxy

Spring は、DelegatingFilterProxy (Javadoc) という名前の Filter 実装を提供します。これにより、サーブレットコンテナーのライフサイクルと Spring の ApplicationContext の間のブリッジが可能になります。サーブレットコンテナーでは、独自の標準を使用して Filter インスタンスを登録できますが、Spring で定義された Bean を認識していません。標準のサーブレットコンテナーメカニズムを介して DelegatingFilterProxy を登録できますが、すべての作業を Filter を実装する Spring Bean に委譲します。

DelegatingFilterProxy が Filter インスタンスと FilterChain にどのように適合するかの図です。

delegatingfilterproxy
図 2: DelegatingFilterProxy

DelegatingFilterProxy は、ApplicationContext から Bean フィルター 0 を検索してから、Bean フィルター 0 を呼び出します。次のリストは、DelegatingFilterProxy の擬似コードを示しています。

DelegatingFilterProxy 擬似コード
  • Java

  • Kotlin

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	Filter delegate = getFilterBean(someBeanName); (1)
	delegate.doFilter(request, response); (2)
}
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
	val delegate: Filter = getFilterBean(someBeanName) (1)
	delegate.doFilter(request, response) (2)
}
1Spring Bean として登録された Filter を遅延取得します。DelegatingFilterProxy の例では、delegate は Bean Filter 0 のインスタンスです。
2 作業を Spring Bean に委譲します。

DelegatingFilterProxy のもう 1 つの利点は、Filter Bean インスタンスの検索を遅らせることができることです。コンテナーを起動する前に、コンテナーが Filter インスタンスを登録する必要があるため、これは重要です。ただし、Spring は通常 ContextLoaderListener を使用して Spring Bean をロードします。これは、Filter インスタンスを登録する必要があるまで実行されません。

FilterChainProxy

Spring Security のサーブレットサポートは FilterChainProxy に含まれています。FilterChainProxy は、SecurityFilterChain を介して多くの Filter インスタンスに委譲できる、Spring Security によって提供される特別な Filter です。FilterChainProxy は Bean であるため、通常は DelegatingFilterProxy にラップされます。

次のイメージは、FilterChainProxy のロールを示しています。

filterchainproxy
図 3: FilterChainProxy

SecurityFilterChain

SecurityFilterChain (Javadoc) は、FilterChainProxy によって使用され、現在のリクエストに対してどの Spring Security Filter インスタンスを呼び出す必要があるかを判別します。

次のイメージは、SecurityFilterChain のロールを示しています。

securityfilterchain
図 4: SecurityFilterChain

SecurityFilterChain のセキュリティフィルターは通常 Bean ですが、DelegatingFilterProxy ではなく FilterChainProxy に登録されます。FilterChainProxy には、サーブレットコンテナーまたは DelegatingFilterProxy に直接登録することで多くの利点があります。まず、Spring Security のすべてのサーブレットサポートの開始点を提供します。そのため、Spring Security のサーブレットサポートのトラブルシューティングを行う場合は、FilterChainProxy にデバッグポイントを追加することから始めるのが最適です。

第 2 に、FilterChainProxy は Spring Security の使用箇所の中心であるため、オプションとは見なされないタスクを実行できます。例: メモリリークを回避するために SecurityContext をクリアします。また、Spring Security の HttpFirewall を適用して、特定の種類の攻撃からアプリケーションを保護します。

さらに、SecurityFilterChain をいつ呼び出すかをより柔軟に決定できます。サーブレットコンテナーでは、Filter インスタンスは URL のみに基づいて呼び出されます。ただし、FilterChainProxy は、RequestMatcher インターフェースを使用して、HttpServletRequest 内のすべてに基づいて呼び出しを判別できます。

次のイメージは、複数の SecurityFilterChain インスタンスを示しています。

multi securityfilterchain
図 5: 複数の SecurityFilterChain

複数の SecurityFilterChain の図では、FilterChainProxy がどの SecurityFilterChain を使用するかを決定します。一致する最初の SecurityFilterChain のみが呼び出されます。/api/messages/ の URL がリクエストされた場合、最初に /api/** の SecurityFilterChain0 パターンと一致するため、SecurityFilterChainn でも一致する場合でも、SecurityFilterChain0 のみが呼び出されます。/messages/ の URL がリクエストされた場合、/api/** の SecurityFilterChain0 パターンと一致しないため、FilterChainProxy は各 SecurityFilterChain の試行を続行します。他の SecurityFilterChain インスタンスが一致しないと仮定すると、SecurityFilterChainn が呼び出されます。

SecurityFilterChain0 には 3 つのセキュリティ Filter インスタンスしか構成されていないことに注意してください。ただし、SecurityFilterChainn には 4 つのセキュリティ Filter インスタンスが構成されています。各 SecurityFilterChain は一意であり、分離して構成できることに注意することが重要です。実際、アプリケーションが Spring Security に特定のリクエストを無視させたい場合、SecurityFilterChain はセキュリティ Filter インスタンスを持たない可能性があります。

セキュリティフィルター

セキュリティフィルターは、SecurityFilterChain API を使用して FilterChainProxy に挿入されます。これらのフィルターは、エクスプロイト保護認証認可など、さまざまな目的に使用できます。フィルターは、適切なタイミングで呼び出されることを保証するために、特定の順序で実行されます。たとえば、認証を実行する Filter は、認可を実行する Filter の前に呼び出される必要があります。通常、Spring Security の Filter の順序を知る必要はありません。ただし、順序を知っておくと便利な場合があります。順序を知りたい場合は、FilterOrderRegistration コード [GitHub] (英語) を確認できます。

これらのセキュリティフィルターは、ほとんどの場合、HttpSecurity (Javadoc) インスタンスを使用して宣言されます。上記の段落を例示するために、次のセキュリティ構成を考えてみましょう。

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults())
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            );

        return http.build();
    }

}
import org.springframework.security.config.web.servlet.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            csrf { }
            httpBasic { }
            formLogin { }
            authorizeHttpRequests {
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

}

上記の構成により、Filter の順序は次のようになります。

フィルター 追加

CsrfFilter

HttpSecurity#csrf

UsernamePasswordAuthenticationFilter

HttpSecurity#formLogin

BasicAuthenticationFilter

HttpSecurity#httpBasic

AuthorizationFilter

HttpSecurity#authorizeHttpRequests

  1. まず、CSRF 攻撃から保護するために CsrfFilter が呼び出されます。

  2. 次に、認証フィルターが呼び出され、リクエストが認証されます。

  3. 3 番目に、リクエストを認可するために AuthorizationFilter が呼び出されます。

上記にリストされていない他の Filter インスタンスが存在する可能性があります。特定のリクエストに対して呼び出されたフィルターのリストを表示したい場合は、出力できます。

セキュリティフィルターを出力する

多くの場合、特定のリクエストに対して呼び出されるセキュリティ Filter のリストを確認すると便利です。例: 追加したフィルターがセキュリティフィルターのリストに含まれていることを確認したい場合。

フィルターのリストはアプリケーションの起動時に DEBUG レベルで出力されるため、たとえばコンソール出力に次のようなものが表示されます。

2023-06-14T08:55:22.321-03:00  DEBUG 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [ DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter, BasicAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter]

これにより、各フィルターチェーンに構成されているセキュリティフィルターについてかなりよく理解できるようになります。

しかし、それだけではありません。リクエストごとに個別のフィルターの呼び出しを出力するようにアプリケーションを構成することもできます。これは、追加したフィルターが特定のリクエストに対して呼び出されているかどうかを確認したり、例外の発生元を確認したりできます。これを行うには、セキュリティイベントをログに記録するようにアプリケーションを構成します。

フィルターチェーンへのフィルターの追加

ほとんどの場合、デフォルトのセキュリティフィルターでアプリケーションのセキュリティを十分に確保できます。ただし、カスタム Filter を SecurityFilterChain に追加したい場合もあります。

HttpSecurity (Javadoc) には、フィルターを追加するための 3 つの方法があります。

  • #addFilterBefore(Filter, Class<?>) は他のフィルターの前にフィルターを追加します

  • #addFilterAfter(Filter, Class<?>) は別のフィルターの後にフィルターを追加します

  • #addFilterAt(Filter, Class<?>) は別のフィルターをあなたのフィルターに置き換えます

カスタムフィルターの追加

独自のフィルターを作成する場合は、フィルターチェーン内での位置を決定する必要があります。フィルターチェーンで発生する次の主要なイベントを確認してください。

  1. SecurityContext はセッションからロードされます

  2. リクエストは一般的なエクスプロイトから保護されています。セキュアヘッダーCORSCSRF

  3. リクエストは認証されまし

  4. リクエストは認可されまし

フィルターを見つけるために、どのようなイベントが発生する必要があるかを検討します。以下は経験則です。

フィルターが その後 これらの出来事はすでに起こっているの

エクスプロイト保護フィルター

SecurityContextHolderFilter

1

認証フィルター

LogoutFilter

1, 2

認証フィルター

AnonymousAuthenticationFilter

1, 2, 3

最も一般的なのは、アプリケーションがカスタム認証を追加することです。つまり、カスタム認証は LogoutFilter の後に配置する必要があります。

たとえば、テナント ID ヘッダーを取得し、現在のユーザーがそのテナントにアクセスできるかどうかを確認する Filter を追加するとします。

まず、Filter を作成しましょう。

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String tenantId = request.getHeader("X-Tenant-Id"); (1)
        boolean hasAccess = isUserAllowed(tenantId); (2)
        if (hasAccess) {
            filterChain.doFilter(request, response); (3)
            return;
        }
        throw new AccessDeniedException("Access denied"); (4)
    }

}

上記のサンプルコードは次のことを行います。

1 リクエストヘッダーからテナント ID を取得します。
2 現在のユーザーがテナント ID にアクセスできるかどうかを確認します。
3 ユーザーがアクセス権を持っている場合は、チェーン内の残りのフィルターを呼び出します。
4 ユーザーにアクセス権がない場合は、AccessDeniedException をスローします。

Filter を実装する代わりに、リクエストごとに 1 回だけ呼び出され、HttpServletRequest および HttpServletResponse パラメーターを備えた doFilterInternal メソッドを提供するフィルターの基本クラスである OncePerRequestFilter (Javadoc) から拡張できます。

ここで、SecurityFilterChain にフィルターを追加する必要があります。前の説明から、フィルターを追加する場所に関するヒントがすでに得られています。現在のユーザーを知る必要があるため、認証フィルターの後に追加する必要があります。

経験則に基づいて、次のように、チェーンの最後の認証フィルターである  AnonymousAuthenticationFilter の後に追加します。

  • Java

  • Kotlin

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); (1)
    return http.build();
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http
        // ...
        .addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) (1)
    return http.build()
}
1HttpSecurity#addFilterAfter を使用して、AnonymousAuthenticationFilter の後に TenantFilter を追加します。

AnonymousAuthenticationFilter の後にフィルターを追加することで、認証フィルターの後に TenantFilter が呼び出されるようになります。

これで、TenantFilter がフィルターチェーンで呼び出され、現在のユーザーがテナント ID にアクセスできるかどうかを確認します。

フィルターを Bean として宣言する

Filter を Spring Bean として宣言する場合、@Component でアノテーションを付ける、または構成で Bean として宣言すると、Spring Boot はそれを埋め込みコンテナーに自動的に登録します。これにより、フィルターが 2 回呼び出される場合があります。1 回はコンテナーによって、もう 1 回は Spring Security によって異なる順序で呼び出されます。

そのため、フィルターは Spring Bean ではないことがよくあります。

ただし、フィルターを Spring Bean にする必要がある場合 (たとえば、依存性注入を利用するため)、FilterRegistrationBean Bean を宣言し、その enabled プロパティを false に設定することで、Spring Boot にコンテナーに登録しないように指示できます。

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}

これにより、HttpSecurity だけがこれを追加することになります。

Spring Security フィルターのカスタマイズ

一般的に、フィルターの DSL メソッドを使用して Spring Security のフィルターを構成できます。例: BasicAuthenticationFilter を追加する最も簡単な方法は、DSL にそれを実行するように依頼することです。

  • Java

  • Kotlin

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		.httpBasic(Customizer.withDefaults())
        // ...

	return http.build();
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
	http {
        httpBasic { }
        // ...
	}

	return http.build()
}

ただし、Spring Security フィルターを自分で構築したい場合は、次のように addFilterAt を使用して DSL で指定します。

  • Java

  • Kotlin

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
	// ... configure

	http
		// ...
		.addFilterAt(basic, BasicAuthenticationFilter.class);

	return http.build();
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
	val basic = BasicAuthenticationFilter()
	// ... configure

	http
		// ...
		.addFilterAt(basic, BasicAuthenticationFilter::class.java)

	return http.build()
}

そのフィルターがすでに追加されている場合、Spring Security は例外をスローすることに注意してください。例:  HttpSecurity#httpBasic を呼び出すと、BasicAuthenticationFilter が追加されます。次の配置は、両方とも BasicAuthenticationFilter を追加しようとする 2 つの呼び出しがあるため失敗します。

  • Java

  • Kotlin

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
	// ... configure

	http
		.httpBasic(Customizer.withDefaults())
		// ... on no! BasicAuthenticationFilter is added twice!
		.addFilterAt(basic, BasicAuthenticationFilter.class);

	return http.build();
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
	val basic = BasicAuthenticationFilter()
	// ... configure

	http {
		httpBasic { }
	}

	// ... on no! BasicAuthenticationFilter is added twice!
    http.addFilterAt(basic, BasicAuthenticationFilter::class.java)

	return http.build()
}

この場合、BasicAuthenticationFilter を自分で構築しているため、httpBasic への呼び出しを削除します。

特定のフィルターを追加しないように HttpSecurity を再構成できない場合は、通常、次のように DSL の disable メソッドを呼び出して Spring Security フィルターを無効にすることができます。

.httpBasic((basic) -> basic.disable())

セキュリティ例外の処理

ExceptionTranslationFilter は、セキュリティフィルターの 1 つとして FilterChainProxy に挿入されます。

次のイメージは、ExceptionTranslationFilter と他のコンポーネントとの関連を示しています。

exceptiontranslationfilter
  • number 1 まず、ExceptionTranslationFilter が FilterChain.doFilter(request, response) を呼び出して、残りのアプリケーションを呼び出します。

  • number 2 ユーザーが認証されていない場合、または AuthenticationException の場合は、認証を開始します。

    • SecurityContextHolder はクリアされます。

    • HttpServletRequest は、認証が成功した後に元のリクエストを再生するために使用できるように保存されます。

    • AuthenticationEntryPoint は、クライアントから資格情報をリクエストするために使用されます。例: ログインページにリダイレクトするか、WWW-Authenticate ヘッダーを送信する場合があります。

  • number 3 それ以外の場合、それが AccessDeniedException の場合、アクセスが拒否されましたAccessDeniedHandler は、拒否されたアクセスを処理するために呼び出されます。

アプリケーションが AccessDeniedException または AuthenticationException をスローしない場合、ExceptionTranslationFilter は何もしません。

ExceptionTranslationFilter の擬似コードは次のようになります。

ExceptionTranslationFilter 擬似コード
try {
	filterChain.doFilter(request, response); (1)
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication(); (2)
	} else {
		accessDenied(); (3)
	}
}
1 フィルターのレビューに従って、FilterChain.doFilter(request, response) を呼び出すことは、アプリケーションの残りの部分を呼び出すことと同じです。これは、アプリケーションの別の部分(FilterSecurityInterceptor またはメソッドセキュリティ)が AuthenticationException または AccessDeniedException をスローした場合、ここでキャッチされて処理されることを意味します。
2 ユーザーが認証されていないか、AuthenticationException の場合は、認証を開始します。
3 それ以外の場合、アクセスは拒否されました

認証間のリクエストの保存

セキュリティ例外の処理に示されているように、リクエストに認証がなく、認証が必要なリソースに対するものである場合、認証が成功した後に再リクエストするために、認証されたリソースのリクエストを保存する必要があります。Spring Security では、これは RequestCache 実装を使用して HttpServletRequest を保存することによって行われます。

RequestCache

HttpServletRequest は RequestCache (Javadoc) に保存されます。ユーザーが正常に認証されると、RequestCache を使用して元のリクエストが再生されます。RequestCacheAwareFilter は、ユーザーが認証した後、RequestCache を使用して保存された HttpServletRequest を取得します。一方、ExceptionTranslationFilter は、AuthenticationException を検出した後、ユーザーをログインエンドポイントにリダイレクトする前に、RequestCache を使用して HttpServletRequest を保存します。

デフォルトでは、HttpSessionRequestCache が使用されます。以下のコードは、continue という名前のパラメーターが存在する場合に、保存されたリクエストの HttpSession をチェックするために使用される RequestCache 実装をカスタマイズする方法を示しています。

 continue パラメーターが存在する場合、RequestCache は保存されたリクエストのみをチェックします
  • Java

  • Kotlin

  • XML

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
    val httpRequestCache = HttpSessionRequestCache()
    httpRequestCache.setMatchingRequestParameterName("continue")
    http {
        requestCache {
            requestCache = httpRequestCache
        }
    }
    return http.build()
}
<http auto-config="true">
	<!-- ... -->
	<request-cache ref="requestCache"/>
</http>

<b:bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache"
	p:matchingRequestParameterName="continue"/>

リクエストが保存されないようにする

ユーザーの認証されていないリクエストをセッションに保存したくない理由はいくつかあります。そのストレージをユーザーのブラウザーにオフロードするか、データベースに保存することができます。または、ユーザーがログイン前にアクセスしようとしたページではなく、常にホームページにリダイレクトする必要があるため、この機能をオフにすることもできます。

そのためには、NullRequestCache (Javadoc) 実装を使用できます。

リクエストが保存されないようにする
  • Java

  • Kotlin

  • XML

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
    val nullRequestCache = NullRequestCache()
    http {
        requestCache {
            requestCache = nullRequestCache
        }
    }
    return http.build()
}
<http auto-config="true">
	<!-- ... -->
	<request-cache ref="nullRequestCache"/>
</http>

<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>

RequestCacheAwareFilter

RequestCacheAwareFilter (Javadoc) RequestCache を使用して元のリクエストを再生します。

ログ

Spring Security は、DEBUG および TRACE レベルですべてのセキュリティ関連イベントの包括的なログを提供します。セキュリティ対策のため、Spring Security はリクエストが拒否された理由の詳細をレスポンス本文に追加しないため、これはアプリケーションをデバッグするときに非常に役立ちます。401 または 403 エラーが発生した場合は、何が起こっているかを理解するのに役立つログメッセージが見つかる可能性が高くなります。

ユーザーが CSRF トークンを使用せずに、CSRF の保護が有効になっているリソースに対して POST リクエストを実行しようとする例を考えてみましょう。ログがないと、ユーザーにはリクエストが拒否された理由の説明のない 403 エラーが表示されます。ただし、Spring Security のログを有効にすると、次のようなログメッセージが表示されます。

2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /hello
2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/15)
2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/15)
2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/15)
2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/15)
2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/15)
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/hello
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl   : Responding with 403 status code
2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

CSRF トークンが見つからないため、リクエストが拒否されていることが明らかになります。

すべてのセキュリティイベントをログに記録するようにアプリケーションを構成するには、アプリケーションに以下を追加します。

application.properties in Spring Boot
logging.level.org.springframework.security=TRACE
logback.xml
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- ... -->
    </appender>
    <!-- ... -->
    <logger name="org.springframework.security" level="trace" additivity="false">
        <appender-ref ref="Console" />
    </logger>
</configuration>