アーキテクチャー
このセクションでは、サーブレットベースのアプリケーション内の Spring Security の高レベルアーキテクチャについて説明します。リファレンスの認証、認可、エクスプロイトに対する保護セクション内のこの高レベルの理解に基づいています。
フィルターのレビュー
Spring Security のサーブレットサポートはサーブレットフィルターに基づいているため、一般的に最初にフィルターのロールを確認すると便利です。次のイメージは、単一の HTTP リクエストのハンドラーの一般的な階層化を示しています。
クライアントはアプリケーションにリクエストを送信し、コンテナーは 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
は、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)
}
1 | Spring 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
のロールを示しています。
SecurityFilterChain
SecurityFilterChain
(Javadoc) は、FilterChainProxy によって使用され、現在のリクエストに対してどの Spring Security Filter
インスタンスを呼び出す必要があるかを判別します。
次のイメージは、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
インスタンスを示しています。
複数の 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
の順序は次のようになります。
フィルター | 追加 |
---|---|
| |
| |
| |
|
まず、CSRF 攻撃から保護するために
CsrfFilter
が呼び出されます。次に、認証フィルターが呼び出され、リクエストが認証されます。
3 番目に、リクエストを認可するために
AuthorizationFilter
が呼び出されます。
上記にリストされていない他の |
セキュリティフィルターを出力する
多くの場合、特定のリクエストに対して呼び出されるセキュリティ 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<?>)
は別のフィルターをあなたのフィルターに置き換えます
カスタムフィルターの追加
独自のフィルターを作成する場合は、フィルターチェーン内での位置を決定する必要があります。フィルターチェーンで発生する次の主要なイベントを確認してください。
フィルターを見つけるために、どのようなイベントが発生する必要があるかを検討します。以下は経験則です。
フィルターが | その後 | これらの出来事はすでに起こっているの |
---|---|---|
エクスプロイト保護フィルター | 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 をスローします。 |
|
ここで、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()
}
1 | HttpSecurity#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
への呼び出しを削除します。
特定のフィルターを追加しないように
|
セキュリティ例外の処理
ExceptionTranslationFilter
(Javadoc) では、AccessDeniedException
(Javadoc) および AuthenticationException
(Javadoc) を HTTP レスポンスに変換できます。
ExceptionTranslationFilter
は、セキュリティフィルターの 1 つとして FilterChainProxy に挿入されます。
次のイメージは、ExceptionTranslationFilter
と他のコンポーネントとの関連を示しています。
まず、
ExceptionTranslationFilter
がFilterChain.doFilter(request, response)
を呼び出して、残りのアプリケーションを呼び出します。ユーザーが認証されていない場合、または
AuthenticationException
の場合は、認証を開始します。SecurityContextHolder はクリアされます。
HttpServletRequest
は、認証が成功した後に元のリクエストを再生するために使用できるように保存されます。AuthenticationEntryPoint
は、クライアントから資格情報をリクエストするために使用されます。例: ログインページにリダイレクトするか、WWW-Authenticate
ヘッダーを送信する場合があります。
それ以外の場合、それが
AccessDeniedException
の場合、アクセスが拒否されました。AccessDeniedHandler
は、拒否されたアクセスを処理するために呼び出されます。
アプリケーションが |
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 トークンが見つからないため、リクエストが拒否されていることが明らかになります。
すべてのセキュリティイベントをログに記録するようにアプリケーションを構成するには、アプリケーションに以下を追加します。
logging.level.org.springframework.security=TRACE
<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>