架构

本节讨论 Spring Security 在基于 Servlet 的应用程序中的高层架构。 我们将在参考文档的认证授权以及防范攻击等章节中,进一步深入阐述这一高层理解。spring-doc.cadn.net.cn

过滤器综述

Spring Security 的 Servlet 支持基于 Servlet 过滤器(Filters),因此首先了解过滤器的一般作用会很有帮助。 下图展示了处理单个 HTTP 请求时典型的处理器分层结构。spring-doc.cadn.net.cn

filterchain
图1. FilterChain

客户端向应用程序发送请求,容器会创建一个 FilterChain,其中包含根据请求 URI 路径应处理 HttpServletRequestFilter 实例和 Servlet。 在 Spring MVC 应用程序中,ServletDispatcherServlet 的一个实例。 最多只能有一个 Servlet 来处理单个 HttpServletRequestHttpServletResponse。 然而,可以使用多个 Filter 来:spring-doc.cadn.net.cn

  • 阻止下游的 Filter 实例或 Servlet 被调用。 在这种情况下,Filter 通常会写入 HttpServletResponsespring-doc.cadn.net.cn

  • 修改下游 HttpServletRequest 实例和 HttpServletResponse 所使用的 FilterServletspring-doc.cadn.net.cn

Filter 的强大之处在于传入其中的 FilterChainspring-doc.cadn.net.cn

FilterChain 使用示例
@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被调用的顺序至关重要。spring-doc.cadn.net.cn

委托过滤器代理

Spring 提供了一个名为 DelegatingFilterProxyFilter 实现,用于在 Servlet 容器的生命周期与 Spring 的 ApplicationContext 之间建立桥接。 Servlet 容器允许使用其自身标准注册 Filter 实例,但它无法识别 Spring 定义的 Bean。 您可以通过标准的 Servlet 容器机制注册 DelegatingFilterProxy,但将所有工作委托给实现了 Filter 的 Spring Bean 来完成。spring-doc.cadn.net.cn

以下是 DelegatingFilterProxy 如何融入 Filter 实例和 FilterChain 的示意图。spring-doc.cadn.net.cn

delegatingfilterproxy
图2. DelegatingFilterProxy

DelegatingFilterProxyApplicationContext 中查找 Bean Filter0,然后调用 Bean Filter0。 以下代码清单展示了 DelegatingFilterProxy 的伪代码:spring-doc.cadn.net.cn

DelegatingFilterProxy 伪代码
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 实例之后才完成的。spring-doc.cadn.net.cn

过滤器链代理

Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。 FilterChainProxy 是 Spring Security 提供的一个特殊 Filter,它允许通过 SecurityFilterChain 委托给多个 Filter 实例。 由于 FilterChainProxy 是一个 Bean,它通常被包装在 DelegatingFilterProxy 中。spring-doc.cadn.net.cn

下图展示了 FilterChainProxy 的作用。spring-doc.cadn.net.cn

filterchainproxy
图3. FilterChainProxy

安全过滤器链

SecurityFilterChainFilterChainProxy 用于确定当前请求应调用哪些 Spring Security Filter 实例。spring-doc.cadn.net.cn

下图展示了 SecurityFilterChain 的作用。spring-doc.cadn.net.cn

securityfilterchain
图4. SecurityFilterChain

#servlet-security-filters 中的安全过滤器通常是 Bean,但它们是注册到 FilterChainProxy,而不是注册到DelegatingFilterProxyFilterChainProxy 相较于直接向 Servlet 容器或DelegatingFilterProxy 注册,提供了多项优势。 首先,它为 Spring Security 所有基于 Servlet 的支持提供了一个统一的入口点。 因此,如果你需要排查 Spring Security 的 Servlet 支持相关问题,在 FilterChainProxy 中添加一个调试断点是一个很好的起点。spring-doc.cadn.net.cn

其次,由于 FilterChainProxy 是 Spring Security 使用的核心,它可以执行一些并非可选的任务。 例如,它会清除 SecurityContext 以避免内存泄漏。 此外,它还应用 Spring Security 的 HttpFirewall 来保护应用程序免受某些类型攻击的影响。spring-doc.cadn.net.cn

此外,它在决定何时应调用 SecurityFilterChain 时提供了更大的灵活性。 在 Servlet 容器中,Filter 实例仅根据 URL 被调用。 然而,FilterChainProxy 可以通过使用 HttpServletRequest 接口,根据 RequestMatcher 中的任意内容来决定是否调用。spring-doc.cadn.net.cn

下图展示了多个 SecurityFilterChain 实例:spring-doc.cadn.net.cn

multi securityfilterchain
图5. 多个 SecurityFilterChain

多个 SecurityFilterChain 图中,FilterChainProxy 决定应使用哪个 SecurityFilterChain。 仅调用第一个匹配的 SecurityFilterChain。 如果请求的 URL 为 /api/messages/,它首先匹配 /api/**SecurityFilterChain0 模式,因此仅调用 SecurityFilterChain0,即使它也匹配 SecurityFilterChainn。 如果请求的 URL 为 /messages/,它不匹配 /api/**SecurityFilterChain0 模式,因此 FilterChainProxy 继续尝试每个 SecurityFilterChain。 假设没有其他 SecurityFilterChain 实例匹配,则调用 SecurityFilterChainnspring-doc.cadn.net.cn

请注意,SecurityFilterChain0 仅配置了三个安全 Filter 实例。 然而,SecurityFilterChainn 则配置了四个安全 Filter 实例。 需要注意的是,每个 SecurityFilterChain 都可以是唯一的,并且可以独立进行配置。 事实上,如果应用程序希望 Spring Security 忽略某些请求,那么一个 SecurityFilterChain 甚至可能不包含任何安全 Filter 实例。spring-doc.cadn.net.cn

安全过滤器

安全过滤器通过 FilterChainProxySecurityFilterChain API 被插入到过滤器链中。 这些过滤器可用于多种不同目的,例如 漏洞防护身份验证授权等。 过滤器的执行顺序是特定的,以确保它们在正确的时间被调用。例如,执行身份验证的 Filter 应在执行授权的 Filter 之前被调用。 通常无需了解 Spring Security 的 Filter 的顺序。 然而,在某些情况下了解其顺序是有益的。如果您想了解它们,可以查看 FilterOrderRegistration 代码spring-doc.cadn.net.cn

这些安全过滤器最常使用 HttpSecurity 实例进行声明。 为了举例说明上述段落,让我们考虑以下安全配置:spring-doc.cadn.net.cn

@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 的执行顺序:spring-doc.cadn.net.cn

过滤器 由...添加

CsrfFilterspring-doc.cadn.net.cn

HttpSecurity#csrfspring-doc.cadn.net.cn

BasicAuthenticationFilterspring-doc.cadn.net.cn

HttpSecurity#httpBasicspring-doc.cadn.net.cn

UsernamePasswordAuthenticationFilterspring-doc.cadn.net.cn

HttpSecurity#formLoginspring-doc.cadn.net.cn

授权过滤器spring-doc.cadn.net.cn

HttpSecurity#authorizeHttpRequestsspring-doc.cadn.net.cn

  1. 首先,调用CsrfFilter以防范CSRF攻击spring-doc.cadn.net.cn

  2. 其次,身份验证过滤器被调用以对请求进行身份验证。spring-doc.cadn.net.cn

  3. 第三,调用 AuthorizationFilter 来授权请求。spring-doc.cadn.net.cn

可能还存在上述未列出的其他 Filter 实例。 如果您想查看针对特定请求所调用的过滤器列表,可以打印它们spring-doc.cadn.net.cn

打印安全过滤器

通常,查看针对特定请求所调用的安全Filter列表非常有用。 例如,您希望确保您添加的过滤器已包含在安全过滤器列表中。spring-doc.cadn.net.cn

过滤器列表会在应用程序启动时以 DEBUG 级别打印出来,因此你可以在控制台输出中看到类似以下的内容:spring-doc.cadn.net.cn

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]

这将很好地展示为每个过滤器链所配置的安全过滤器。spring-doc.cadn.net.cn

但这还不是全部,你还可以配置你的应用程序,以打印每个请求所调用的各个过滤器。 这有助于确认你添加的过滤器是否针对某个特定请求被调用,或者用于排查异常的来源。 为此,你可以配置你的应用程序记录安全事件spring-doc.cadn.net.cn

将过滤器添加到过滤器链

大多数情况下,默认的安全过滤器(Security Filters)已足以为您的应用程序提供安全保障。 然而,有时您可能希望向SecurityFilterChain中添加自定义的#servlet-securityfilterchainspring-doc.cadn.net.cn

HttpSecurity 提供三种添加过滤器方法:spring-doc.cadn.net.cn

添加自定义过滤器

如果你正在创建自己的过滤器,就需要确定它在过滤器链中的位置。 请查看以下在过滤器链中发生的关键事件:spring-doc.cadn.net.cn

  1. SecurityContext 已从会话中加载spring-doc.cadn.net.cn

  2. 请求受到保护,可防范常见攻击;安全头CORSCSRFspring-doc.cadn.net.cn

  3. 请求已认证spring-doc.cadn.net.cn

  4. 请求已授权spring-doc.cadn.net.cn

考虑一下,为了定位你的过滤器,需要发生哪些事件。 以下是一条经验法则:spring-doc.cadn.net.cn

如果你的过滤器是一个 然后将其放置在之后 由于这些事件已经发生

漏洞利用防护过滤器spring-doc.cadn.net.cn

SecurityContextHolderFilterspring-doc.cadn.net.cn

1spring-doc.cadn.net.cn

身份验证过滤器spring-doc.cadn.net.cn

注销过滤器spring-doc.cadn.net.cn

1, 2spring-doc.cadn.net.cn

授权过滤器spring-doc.cadn.net.cn

匿名认证过滤器spring-doc.cadn.net.cn

1, 2, 3spring-doc.cadn.net.cn

最常见的是,应用程序添加自定义身份验证。 这意味着它们应放置在 LogoutFilter 之后。

例如,假设你想添加一个Filter,用于获取租户 ID 的请求头,并检查当前用户是否拥有对该租户的访问权限。spring-doc.cadn.net.cn

首先,让我们创建这个 Filterspring-doc.cadn.net.cn

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)
    }

}

上面的示例代码执行了以下操作:spring-doc.cadn.net.cn

1 从请求头中获取租户 ID。
2 检查当前用户是否具有访问租户ID的权限。
3 如果用户拥有访问权限,则调用链中其余的过滤器。
4 如果用户没有访问权限,则抛出 AccessDeniedException

你可以不实现 Filter 接口,而是继承 OncePerRequestFilter 类。该类是专为每个请求仅调用一次的过滤器提供的基类,并提供了一个带有 doFilterInternalHttpServletRequest 参数的 HttpServletResponse 方法。spring-doc.cadn.net.cn

现在,你需要将该过滤器添加到 SecurityFilterChain 中。 前面的描述已经为我们提供了添加过滤器位置的线索:由于我们需要知道当前用户,因此需要将其添加在身份验证过滤器之后。spring-doc.cadn.net.cn

根据经验法则,将其添加到 AnonymousAuthenticationFilter 之后,即链中的最后一个认证过滤器,如下所示:spring-doc.cadn.net.cn

@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#addFilterAfterTenantFilter 添加到 AnonymousAuthenticationFilter 之后。

通过在 AnonymousAuthenticationFilter 之后添加过滤器,我们可以确保 TenantFilter 在认证过滤器之后被调用。spring-doc.cadn.net.cn

就是这样,现在 TenantFilter 将在过滤器链中被调用,并检查当前用户是否具有访问该租户 ID 的权限。spring-doc.cadn.net.cn

将您的过滤器声明为 Bean

当你将一个 Filter 声明为 Spring Bean(无论是通过使用 @Component 注解,还是在配置中显式声明为 Bean),Spring Boot 会自动将其注册到内嵌容器中。 这可能导致该过滤器被调用两次:一次由容器调用,另一次由 Spring Security 调用,并且调用顺序可能不同。spring-doc.cadn.net.cn

因此,过滤器通常不是 Spring Bean。spring-doc.cadn.net.cn

然而,如果你的过滤器需要作为一个 Spring Bean(例如,为了利用依赖注入),你可以通过声明一个 FilterRegistrationBean Bean 并将其 enabled 属性设置为 false,来告知 Spring Boot 不要将其注册到容器中:spring-doc.cadn.net.cn

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

这样就能确保只有 HttpSecurity 会添加它。spring-doc.cadn.net.cn

自定义 Spring Security 过滤器

通常,你可以使用过滤器的 DSL 方法来配置 Spring Security 的过滤器。 例如,添加 BasicAuthenticationFilter 最简单的方法就是让 DSL 来完成这项工作:spring-doc.cadn.net.cn

@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 中进行指定,如下所示:spring-doc.cadn.net.cn

@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。 因此,以下配置会失败,因为有两个调用都试图添加 BasicAuthenticationFilterspring-doc.cadn.net.cn

@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()
}

在这种情况下,请移除对 httpBasic 的调用,因为您正在自行构建 BasicAuthenticationFilterspring-doc.cadn.net.cn

如果你无法重新配置 HttpSecurity 以避免添加某个特定的过滤器,通常可以通过调用该过滤器对应 DSL 的 disable 方法来禁用 Spring Security 过滤器,如下所示:spring-doc.cadn.net.cn

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

处理安全异常

ExceptionTranslationFilter 作为安全过滤器之一被插入到FilterChainProxy中。spring-doc.cadn.net.cn

下图展示了 ExceptionTranslationFilter 与其他组件之间的关系:spring-doc.cadn.net.cn

exceptiontranslationfilter

如果应用程序未抛出 AccessDeniedExceptionAuthenticationException,则 ExceptionTranslationFilter 不会执行任何操作。spring-doc.cadn.net.cn

ExceptionTranslationFilter 的伪代码大致如下所示:spring-doc.cadn.net.cn

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 完成的。spring-doc.cadn.net.cn

请求缓存

HttpServletRequest 被保存在 RequestCache 中。 当用户成功通过身份验证时,RequestCache 用于重放原始请求。 RequestCacheAwareFilter 在用户通过身份验证后使用 RequestCache 获取保存的 HttpServletRequest,而 ExceptionTranslationFilter 在检测到 AuthenticationException 后使用 RequestCache 保存 HttpServletRequest,然后将用户重定向到登录端点。spring-doc.cadn.net.cn

默认情况下,会使用 HttpSessionRequestCache。 以下代码演示了如何自定义所使用的 RequestCache 实现,以便在存在名为 HttpSession 的参数时,检查 continue 中是否保存了请求。spring-doc.cadn.net.cn

RequestCache 仅在存在 continue 参数时才会检查已保存的请求
@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"/>

防止请求被保存

你可能有多种原因不想将用户的未认证请求存储在会话(session)中。 你可能希望将该存储卸载到用户的浏览器中,或者将其存储在数据库中。 又或者,你可能希望完全关闭此功能,因为你总是希望将用户重定向到首页,而不是他们登录前尝试访问的页面。spring-doc.cadn.net.cn

为此,您可以使用 NullRequestCache 实现。spring-doc.cadn.net.cn

防止请求被保存
@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"/>

请求缓存感知过滤器

日志记录

Spring Security 在 DEBUG 和 TRACE 级别对所有与安全相关的事件提供全面的日志记录。 这在调试应用程序时非常有用,因为出于安全考虑,Spring Security 不会在响应体中包含请求被拒绝的具体原因。 如果你遇到 401 或 403 错误,很可能会在日志中找到有助于理解问题所在的消息。spring-doc.cadn.net.cn

让我们考虑一个示例:用户尝试向启用了CSRF保护的资源发送exploits/csrf.html请求,但未提供CSRFTokens。 如果没有日志记录,用户将看到一个403错误,却无法得知请求被拒绝的原因。 然而,如果你启用了Spring Security的日志记录,你将会看到类似如下的日志消息:spring-doc.cadn.net.cn

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 Tokens缺失,因此该请求被拒绝。spring-doc.cadn.net.cn

要将您的应用程序配置为记录所有安全事件,您可以在应用程序中添加以下内容:spring-doc.cadn.net.cn

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>