架構

本節討論 Spring Security 在基於 Servlet 的應用程式中的高階架構。我們將在參考文件的 認證授權防範攻擊 各節中,在此高階理解的基礎上進行深入探討。

過濾器回顧

Spring Security 的 Servlet 支援基於 Servlet 過濾器,因此首先了解過濾器的作用很有幫助。下圖顯示了單個 HTTP 請求的處理程式的典型分層。

filterchain
圖 1. FilterChain

客戶端嚮應用程式傳送請求,容器根據請求 URI 的路徑建立一個 FilterChain,其中包含應處理 HttpServletRequestFilter 例項和 Servlet。在 Spring MVC 應用程式中,ServletDispatcherServlet 的一個例項。最多隻有一個 Servlet 可以處理單個 HttpServletRequestHttpServletResponse。但是,可以使用多個 Filter

  • 阻止下游 Filter 例項或 Servlet 被呼叫。在這種情況下,Filter 通常會寫入 HttpServletResponse

  • 修改下游 Filter 例項和 Servlet 使用的 HttpServletRequestHttpServletResponse

Filter 的強大之處在於傳遞給它的 FilterChain

FilterChain 使用示例
  • Java

  • Kotlin

@Override
public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {
	// 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
}
@Throws(IOException::class, ServletException::class)
override 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 提供了一個名為 DelegatingFilterProxyFilter 實現,它允許在 Servlet 容器的生命週期和 Spring 的 ApplicationContext 之間架起橋樑。Servlet 容器允許使用其自身的標準註冊 Filter 例項,但它不瞭解 Spring 定義的 Bean。您可以透過標準 Servlet 容器機制註冊 DelegatingFilterProxy,但將所有工作委託給實現 Filter 的 Spring Bean。

下圖顯示了 DelegatingFilterProxy 如何融入 Filter 例項和 FilterChain

delegatingfilterproxy
圖 2. DelegatingFilterProxy

DelegatingFilterProxyApplicationContext 中查詢 Bean Filter0,然後呼叫 Bean Filter0。以下清單顯示了 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 中的示例,delegateBean Filter0 的一個例項。
2 將工作委託給 Spring Bean。

DelegatingFilterProxy 的另一個好處是它允許延遲查詢 Filter bean 例項。這很重要,因為容器需要在啟動之前註冊 Filter 例項。然而,Spring 通常使用 ContextLoaderListener 載入 Spring Bean,這直到 Filter 例項需要註冊之後才完成。

FilterChainProxy

Spring Security 的 Servlet 支援包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一個特殊 Filter,它允許透過 SecurityFilterChain 將工作委託給許多 Filter 例項。由於 FilterChainProxy 是一個 Bean,它通常被包裝在 DelegatingFilterProxy 中。

下圖顯示了 FilterChainProxy 的作用。

filterchainproxy
圖 3. FilterChainProxy

SecurityFilterChain

SecurityFilterChainFilterChainProxy 使用,用於確定應為當前請求呼叫哪些 Spring Security Filter 例項。

下圖顯示了 SecurityFilterChain 的作用。

securityfilterchain
圖 4. SecurityFilterChain

SecurityFilterChain 中的 安全過濾器 通常是 Bean,但它們註冊到 FilterChainProxy 而不是 DelegatingFilterProxyFilterChainProxy 相比直接註冊到 Servlet 容器或 DelegatingFilterProxy 提供了許多優勢。首先,它為 Spring Security 的所有 Servlet 支援提供了一個起點。因此,如果您嘗試對 Spring Security 的 Servlet 支援進行故障排除,在 FilterChainProxy 中新增一個除錯點是一個很好的開始。

其次,由於 FilterChainProxy 是 Spring Security 使用的核心,它可以執行非可選的任務。例如,它清除 SecurityContext 以避免記憶體洩漏。它還應用 Spring Security 的 HttpFirewall 來保護應用程式免受某些型別的攻擊。

此外,它在確定何時應呼叫 SecurityFilterChain 方面提供了更大的靈活性。在 Servlet 容器中,Filter 例項僅根據 URL 呼叫。然而,FilterChainProxy 可以透過使用 RequestMatcher 介面根據 HttpServletRequest 中的任何內容確定呼叫。

下圖顯示了多個 SecurityFilterChain 例項

multi securityfilterchain
圖 5. 多個 SecurityFilterChain

多個 SecurityFilterChain 圖中,FilterChainProxy 決定應該使用哪個 SecurityFilterChain。只有第一個匹配的 SecurityFilterChain 被呼叫。如果請求的 URL 是 /api/messages/,它首先匹配 SecurityFilterChain0 的模式 /api/**,因此只調用 SecurityFilterChain0,即使它也匹配 SecurityFilterChainn。如果請求的 URL 是 /messages/,它不匹配 SecurityFilterChain0 的模式 /api/**,因此 FilterChainProxy 繼續嘗試每個 SecurityFilterChain。假設沒有其他 SecurityFilterChain 例項匹配,則呼叫 SecurityFilterChainn

請注意,SecurityFilterChain0 只配置了三個安全 Filter 例項。然而,SecurityFilterChainn 配置了四個安全 Filter 例項。重要的是要注意,每個 SecurityFilterChain 都可以是唯一的,並且可以獨立配置。實際上,如果應用程式希望 Spring Security 忽略某些請求,SecurityFilterChain 可能包含零個安全 Filter 例項。

安全過濾器

安全過濾器透過 SecurityFilterChain API 插入到 FilterChainProxy 中。這些過濾器可用於多種不同的目的,例如 漏洞防護身份驗證授權 等。過濾器以特定順序執行,以確保它們在正確的時間被呼叫,例如,執行身份驗證的 Filter 應在執行授權的 Filter 之前被呼叫。通常不需要知道 Spring Security Filter 的順序。但是,有時瞭解順序會很有益,如果您想了解它們,可以檢視 FilterOrderRegistration 程式碼

這些安全過濾器最常使用 HttpSecurity 例項宣告。為了說明上一段,我們考慮以下安全配置

  • 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

BasicAuthenticationFilter

HttpSecurity#httpBasic

UsernamePasswordAuthenticationFilter

HttpSecurity#formLogin

AuthorizationFilter

HttpSecurity#authorizeHttpRequests

  1. 首先,呼叫 CsrfFilter 以防範 CSRF 攻擊

  2. 其次,呼叫認證過濾器以認證請求。

  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]

這將很好地說明為每個過濾器鏈配置的安全過濾器。

但這並非全部,您還可以配置應用程式,使其為每個請求列印每個獨立過濾器的呼叫。這有助於檢視您新增的過濾器是否為特定請求呼叫,或檢查異常的來源。為此,您可以配置應用程式以記錄安全事件

將過濾器新增到過濾器鏈

大多數情況下,預設的安全過濾器足以為您的應用程式提供安全保障。但是,有時您可能希望向SecurityFilterChain新增自定義 Filter

HttpSecurity 帶有三個新增過濾器的方法

  • #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 之後。

例如,假設您想要新增一個 Filter,它獲取租戶 ID 標頭並檢查當前使用者是否具有訪問該租戶的許可權。

首先,我們建立 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

您可以透過擴充套件 OncePerRequestFilter 來代替實現 Filter,它是一個基類,用於每個請求僅呼叫一次的過濾器,並提供一個帶有 HttpServletRequestHttpServletResponse 引數的 doFilterInternal 方法。

現在,您需要將過濾器新增到 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#addFilterAfterAnonymousAuthenticationFilter 之後新增 TenantFilter

透過在 AnonymousAuthenticationFilter 之後新增過濾器,我們確保 TenantFilter 在認證過濾器之後被呼叫。

就這樣,現在 TenantFilter 將在過濾器鏈中被呼叫,並將檢查當前使用者是否具有訪問租戶 ID 的許可權。

將您的過濾器宣告為 Bean

當您將 Filter 宣告為 Spring bean 時,無論是透過使用 @Component 註解還是在您的配置中將其宣告為 bean,Spring Boot 都會自動將其註冊到嵌入式容器。這可能導致過濾器被呼叫兩次,一次由容器呼叫,一次由 Spring Security 呼叫,並且順序不同。

因此,過濾器通常不是 Spring bean。

但是,如果您的過濾器需要成為一個 Spring Bean(例如,為了利用依賴注入),您可以告訴 Spring Boot 不要將其註冊到容器中,方法是宣告一個 FilterRegistrationBean Bean 並將其 enabled 屬性設定為 false

@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

  • 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 作為安全過濾器之一插入到FilterChainProxy中。

下圖顯示了 ExceptionTranslationFilter 與其他元件的關係

exceptiontranslationfilter
  • 數字 1 首先,ExceptionTranslationFilter 呼叫 FilterChain.doFilter(request, response) 來呼叫應用程式的其餘部分。

  • 數字 2 如果使用者未認證或發生 AuthenticationException,則開始認證

    • SecurityContextHolder 被清除。

    • HttpServletRequest儲存,以便在認證成功後可以重放原始請求。

    • AuthenticationEntryPoint 用於從客戶端請求憑據。例如,它可能重定向到登入頁面或傳送 WWW-Authenticate 頭。

  • 數字 3 否則,如果發生 AccessDeniedException,則訪問被拒絕。呼叫 AccessDeniedHandler 處理訪問被拒絕。

如果應用程式沒有丟擲 AccessDeniedExceptionAuthenticationException,那麼 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) 等同於呼叫應用程式的其餘部分。這意味著如果應用程式的其他部分(AuthorizationFilter 或方法安全性)丟擲 AuthenticationExceptionAccessDeniedException,它將在此處被捕獲和處理。
2 如果使用者未認證或發生 AuthenticationException,則開始認證
3 否則,訪問被拒絕

認證之間儲存請求

正如 處理安全異常 中所述,當請求沒有認證並且請求的資源需要認證時,需要儲存認證資源請求,以便在認證成功後重新請求。在 Spring Security 中,這是透過使用 RequestCache 實現儲存 HttpServletRequest 來完成的。

RequestCache

HttpServletRequest 儲存在 RequestCache 中。當用戶成功認證後,RequestCache 用於重放原始請求。RequestCacheAwareFilter 在使用者認證後使用 RequestCache 獲取儲存的 HttpServletRequest,而 ExceptionTranslationFilter 在檢測到 AuthenticationException 後,將使用者重定向到登入端點之前,使用 RequestCache 儲存 HttpServletRequest

預設情況下,使用 HttpSessionRequestCache。以下程式碼演示瞭如何自定義 RequestCache 實現,該實現檢查 HttpSession 中是否存在儲存的請求(如果存在名為 continue 的引數)。

僅當 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 實現。

阻止請求被儲存
  • 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 使用 RequestCache 重放原始請求。

日誌

Spring Security 提供所有安全相關事件的全面日誌記錄,級別為 DEBUG 和 TRACE。這在除錯應用程式時非常有用,因為出於安全考慮,Spring Security 不會在響應正文中新增任何關於請求被拒絕的詳細資訊。如果您遇到 401 或 403 錯誤,您很可能會找到一條日誌訊息,幫助您瞭解發生了什麼。

我們考慮一個示例,其中使用者嘗試向啟用了 CSRF 保護 的資源發出 POST 請求,但沒有 CSRF 令牌。在沒有日誌的情況下,使用者將看到 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 https://: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 令牌缺失,這就是請求被拒絕的原因。

要配置您的應用程式以記錄所有安全事件,您可以將以下內容新增到您的應用程式中

Spring Boot 中的 application.properties
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>
© . This site is unofficial and not affiliated with VMware.