跳到主要内容
版本:7.0.2

跨站请求伪造 (CSRF)

DeepSeek V3 中英对照 Cross Site Request Forgery (CSRF)

在允许终端用户登录的应用程序中,考虑如何防范跨站请求伪造(CSRF)至关重要。

Spring Security 默认会为不安全的 HTTP 方法(例如 POST 请求)提供 CSRF 攻击防护,因此无需编写额外代码。您可以使用以下方式显式指定默认配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf(Customizer.withDefaults());
return http.build();
}
}

要了解更多关于应用程序的CSRF防护,请参考以下用例:

理解 CSRF 保护的组成部分

CSRF保护由多个组件共同实现,这些组件集成在CsrfFilter中:

csrf

图 1. CsrfFilter 组件

CSRF 防护分为两部分:

  1. 通过委托给 CsrfTokenRequestHandler,使应用程序能够获取 CsrfToken

  2. 判断请求是否需要 CSRF 保护,加载并验证令牌,并处理 AccessDeniedException

csrf 处理

图 2. CsrfFilter 处理流程

  • 1 首先,加载 DeferredCsrfToken,它持有对 CsrfTokenRepository 的引用,以便稍后(在 4 中)加载持久化的 CsrfToken

  • 2 其次,将一个 Supplier<CsrfToken>(由 DeferredCsrfToken 创建)提供给 CsrfTokenRequestHandler,该处理器负责填充一个请求属性,使 CsrfToken 可供应用程序的其余部分使用。

  • 3 接下来,主要的 CSRF 保护处理开始,并检查当前请求是否需要 CSRF 保护。如果不需要,则继续过滤器链并结束处理。

  • 4 如果需要 CSRF 保护,则最终从 DeferredCsrfToken 加载持久化的 CsrfToken

  • 5 继续处理,使用 CsrfTokenRequestHandler 解析客户端提供的实际 CSRF 令牌(如果有的话)。

  • 6 将实际 CSRF 令牌与持久化的 CsrfToken 进行比较。如果有效,则继续过滤器链并结束处理。

  • 7 如果实际 CSRF 令牌无效(或缺失),则将 AccessDeniedException 传递给 AccessDeniedHandler 并结束处理。

迁移到 Spring Security 6

从 Spring Security 5 迁移到 6 时,有一些变化可能会影响您的应用程序。以下是 Spring Security 6 中 CSRF 保护方面发生变化的概述:

提示

Spring Security 6 的变更要求对单页应用进行额外配置,因此你可能会发现 单页应用 这一节特别有用。

有关迁移 Spring Security 5 应用程序的更多信息,请参阅迁移章节中的漏洞防护部分。

持久化 CsrfToken

CsrfToken 通过 CsrfTokenRepository 进行持久化存储。

默认情况下,HttpSessionCsrfTokenRepository 用于在会话中存储令牌。Spring Security 还提供了 CookieCsrfTokenRepository 用于在 Cookie 中存储令牌。您也可以指定 自己的实现 来在任意位置存储令牌。

使用 HttpSessionCsrfTokenRepository

默认情况下,Spring Security 使用 HttpSessionCsrfTokenRepository 将预期的 CSRF 令牌存储在 HttpSession 中,因此无需编写额外的代码。

HttpSessionCsrfTokenRepository 从会话(无论是内存、缓存还是数据库)中读取令牌。如果你需要直接访问会话属性,请首先使用 HttpSessionCsrfTokenRepository#setSessionAttributeName 配置会话属性名称。

您可以使用以下配置显式指定默认配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
);
return http.build();
}
}

你可以使用 CookieCsrfTokenRepositoryCsrfToken 持久化到 cookie 中,以支持基于 JavaScript 的应用程序

CookieCsrfTokenRepository 默认会向名为 XSRF-TOKEN 的 Cookie 写入数据,并从名为 X-XSRF-TOKEN 的 HTTP 请求头或请求参数 _csrf 中读取数据。这些默认值源自 Angular 及其前身 AngularJS

提示

有关此主题的最新信息,请参阅 HttpClient XSRF/CSRF 安全withXsrfConfiguration

你可以通过以下配置来配置 CookieCsrfTokenRepository

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
备注

此示例显式地将 HttpOnly 设置为 false。这是为了让 JavaScript 框架(例如 Angular)能够读取它。如果您不需要直接用 JavaScript 读取 cookie 的能力,我们建议省略 HttpOnly(通过使用 new CookieCsrfTokenRepository() 替代),以提高安全性。

自定义 CsrfTokenRepository

在某些情况下,您可能需要实现自定义的 CsrfTokenRepository

一旦你实现了 CsrfTokenRepository 接口,就可以通过以下配置让 Spring Security 使用它:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(new CustomCsrfTokenRepository())
);
return http.build();
}
}

处理 CsrfToken

CsrfToken 通过 CsrfTokenRequestHandler 提供给应用程序使用。该组件还负责从 HTTP 头部或请求参数中解析 CsrfToken

默认情况下,XorCsrfTokenRequestAttributeHandler 用于为 CsrfToken 提供 BREACH 攻击防护。Spring Security 也提供了 CsrfTokenRequestAttributeHandler 以选择退出 BREACH 防护。您还可以指定 自己的实现 来自定义处理和解析令牌的策略。

使用 XorCsrfTokenRequestAttributeHandler (BREACH)

XorCsrfTokenRequestAttributeHandlerCsrfToken 作为名为 _csrfHttpServletRequest 属性提供,并额外为 BREACH 攻击提供防护。

备注

CsrfToken 也会以请求属性的形式提供,其属性名称为 CsrfToken.class.getName()。此名称不可配置,但可以通过 XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName 来更改 _csrf 这个名称。

此实现还会从请求中解析令牌值,来源可以是请求头(默认为 X-CSRF-TOKENX-XSRF-TOKEN 之一)或请求参数(默认为 _csrf)。

备注

通过在 CSRF 令牌值中编码随机性来提供 BREACH 保护,确保每次请求返回的 CsrfToken 都会发生变化。当后续将令牌解析为标头值或请求参数时,会对其进行解码以获取原始令牌,然后与持久化的 CsrfToken 进行比较。

Spring Security 默认会保护 CSRF 令牌免受 BREACH 攻击,因此无需编写额外代码。您可以使用以下配置明确指定默认配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
);
return http.build();
}
}

使用 CsrfTokenRequestAttributeHandler

CsrfTokenRequestAttributeHandlerCsrfToken 作为名为 _csrfHttpServletRequest 属性提供。

备注

CsrfToken 也会以请求属性的形式提供,其属性名称为 CsrfToken.class.getName()。此名称不可配置,但可以通过 CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName 来更改 _csrf 这一名称。

此实现还会从请求中解析令牌值,来源可以是请求头(默认是 X-CSRF-TOKENX-XSRF-TOKEN 之一)或请求参数(默认为 _csrf)。

CsrfTokenRequestAttributeHandler 的主要用途是选择退出 CsrfToken 的 BREACH 保护,这可以通过以下配置进行设置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
return http.build();
}
}

自定义 CsrfTokenRequestHandler

你可以实现 CsrfTokenRequestHandler 接口来自定义处理与解析令牌的策略。

提示

CsrfTokenRequestHandler 接口是一个 @FunctionalInterface,可以通过 lambda 表达式实现以自定义请求处理。您需要实现完整的接口来自定义如何从请求中解析令牌。有关使用委托来实现自定义令牌处理和解析策略的示例,请参阅为单页应用程序配置 CSRF

一旦你实现了 CsrfTokenRequestHandler 接口,就可以通过以下配置让 Spring Security 使用它:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler())
);
return http.build();
}
}

延迟加载 CsrfToken

默认情况下,Spring Security 会延迟加载 CsrfToken,直到需要时才进行加载。

备注

每当使用不安全的 HTTP 方法(例如 POST)发起请求时,都需要 CsrfToken。此外,任何需要在响应中渲染令牌的请求也需要它,例如包含隐藏 CSRF 令牌 <input> 标签的 <form> 表单的网页。

由于Spring Security默认也会将CsrfToken存储在HttpSession中,延迟加载CSRF令牌可以通过避免在每个请求中加载会话,从而提升性能。

如果您希望退出延迟令牌,并让 CsrfToken 在每次请求时都加载,可以通过以下配置实现:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null);
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(requestHandler)
);
return http.build();
}
}
备注

csrfRequestAttributeName 设置为 null 时,必须先加载 CsrfToken 以确定要使用的属性名称。这会导致 CsrfToken 在每次请求时都被加载。

与 CSRF 保护集成

对于同步令牌模式来说,要有效防御 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。该令牌必须置于请求的某个部分(如表单参数、HTTP 头部或其他部分),且该部分不会被浏览器自动包含在 HTTP 请求中。

以下部分描述了前端或客户端应用程序与受 CSRF 保护的后端应用程序集成的各种方式:

HTML 表单

要提交 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。例如,渲染后的 HTML 可能如下所示:

<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>

以下视图技术会自动在具有不安全HTTP方法(如POST)的表单中包含实际的CSRF令牌:

如果这些选项不可用,您可以利用CsrfToken作为名为_csrf的HttpServletRequest属性公开的特性。以下示例通过JSP实现了这一点:

<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form>

JavaScript 应用程序

JavaScript应用程序通常使用JSON而非HTML。如果使用JSON,您可以将CSRF令牌放在HTTP请求头中提交,而不是作为请求参数。

为了获取CSRF令牌,你可以配置Spring Security将预期的CSRF令牌存储在cookie中。通过将预期令牌存储在cookie中,像Angular这样的JavaScript框架可以自动将实际的CSRF令牌作为HTTP请求头包含进去。

提示

将单页应用(SPA)与 Spring Security 的 CSRF 保护集成时,对于 BREACH 保护和延迟令牌有特殊注意事项。完整的配置示例请参见下一节

你可以在以下章节中阅读有关不同类型 JavaScript 应用程序的内容:

单页应用程序

将单页应用程序(SPA)与 Spring Security 的 CSRF 防护集成时,存在一些特殊的注意事项。

请注意,Spring Security 默认提供 BREACH 对 CsrfToken 的保护。当将预期的 CSRF 令牌存储在 cookie 中时,JavaScript 应用程序将只能访问纯令牌值,而无法访问编码后的值。因此,需要提供一个自定义请求处理器来解析实际的令牌值。

此外,存储CSRF令牌的Cookie将在认证成功和登出成功时被清除。Spring Security默认会延迟加载新的CSRF令牌,需要额外的工作才能返回一个新的Cookie。

备注

在认证成功和登出成功后刷新令牌是必需的,因为 CsrfAuthenticationStrategyCsrfLogoutHandler 会清除之前的令牌。客户端应用程序将无法执行不安全的 HTTP 请求(例如 POST),除非获取一个新的令牌。

为了轻松地将单页应用与 Spring Security 集成,可以使用以下配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf.spa());
return http.build();
}
}

多页面应用

对于多页面应用,如果每个页面都加载了JavaScript,除了将CSRF令牌存储在cookie中之外,另一种方法是将CSRF令牌包含在meta标签内。HTML代码可能如下所示:

<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
</html>

为了在请求中包含CSRF令牌,您可以利用CsrfToken作为名为_csrf的HttpServletRequest属性公开的特性。以下示例通过JSP实现此操作:

<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<!-- ... -->
</html>

一旦元标签包含CSRF令牌,JavaScript代码就可以读取元标签并将CSRF令牌作为请求头包含。如果使用jQuery,可以通过以下代码实现:

$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});

其他 JavaScript 应用

JavaScript应用程序的另一种选择是将CSRF令牌包含在HTTP响应头中。

实现这一点的一种方法是使用 @ControllerAdvice 配合 CsrfTokenArgumentResolver。以下是一个适用于应用程序中所有控制器端点的 @ControllerAdvice 示例:

@ControllerAdvice
public class CsrfControllerAdvice {

@ModelAttribute
public void getCsrfToken(HttpServletResponse response, CsrfToken csrfToken) {
response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
}

}
备注

由于这个 @ControllerAdvice 会应用到应用程序中的所有端点,它将导致每个请求都加载 CSRF 令牌,这可能会抵消使用 HttpSessionCsrfTokenRepository延迟令牌的优势。然而,在使用 CookieCsrfTokenRepository 时,这通常不是问题。

备注

请务必记住,控制器端点(controller endpoints)和控制器通知(controller advice)是在 Spring Security 过滤器链之后调用的。这意味着,只有当请求通过过滤器链到达您的应用程序时,此 @ControllerAdvice 才会生效。有关在过滤器链中添加过滤器以更早访问 HttpServletResponse 的示例,请参阅单页应用程序的配置。

CSRF令牌现在将在响应头中可用(默认为X-CSRF-TOKENX-XSRF-TOKEN),适用于控制器通知所应用的所有自定义端点。任何对后端的请求都可用于从响应中获取令牌,后续请求可在同名请求头中包含该令牌。

移动应用程序

JavaScript应用类似,移动应用通常使用JSON而非HTML。处理浏览器流量的后端应用可以选择禁用CSRF。在这种情况下,无需额外操作。

然而,一个同样服务于浏览器流量并因此仍需 CSRF 保护的后端应用,可以继续将会话中的 CsrfToken 存储在会话中,而不是存储在 Cookie 中。

在这种情况下,与后端集成的典型模式是暴露一个 /csrf 端点,允许前端(移动端或浏览器客户端)按需请求 CSRF 令牌。使用这种模式的好处是,CSRF 令牌可以继续被延迟加载,并且仅在请求需要 CSRF 保护时才需要从会话中加载。使用自定义端点还意味着,客户端应用程序可以通过发出显式请求,按需(如果需要)请求生成新的令牌。

提示

此模式适用于任何需要CSRF防护的应用程序类型,不仅限于移动应用。虽然在其他场景中通常不需要采用这种方法,但它为与具备CSRF防护的后端集成提供了另一种可选方案。

以下是使用 CsrfTokenArgumentResolver/csrf 端点示例:

@RestController
public class CsrfController {

@GetMapping("/csrf")
public CsrfToken csrf(CsrfToken csrfToken) {
return csrfToken;
}

}
备注

如果上述端点需要在与服务器进行身份验证之前访问,您可以考虑添加 .requestMatchers("/csrf").permitAll()

该端点应在应用程序启动或初始化时(例如在加载时)调用以获取CSRF令牌,同时在身份验证成功和注销成功后也应调用。

备注

在认证成功和登出成功后刷新令牌是必需的,因为 CsrfAuthenticationStrategyCsrfLogoutHandler 会清除之前的令牌。如果不获取新的令牌,客户端应用程序将无法执行不安全的 HTTP 请求,例如 POST 请求。

获取CSRF令牌后,您需要自行将其作为HTTP请求头包含在请求中(默认情况下使用X-CSRF-TOKENX-XSRF-TOKEN之一)。

处理 AccessDeniedException

要处理诸如 InvalidCsrfTokenException 之类的 AccessDeniedException,你可以配置 Spring Security 以任何你喜欢的方式来处理这些异常。例如,你可以使用以下配置来设置一个自定义的访问被拒绝页面:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling((exceptionHandling) -> exceptionHandling
.accessDeniedPage("/access-denied")
);
return http.build();
}
}

CSRF 测试

您可以使用 Spring Security 的测试支持CsrfRequestPostProcessor来测试 CSRF 防护,如下所示:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SecurityConfig.class)
@WebAppConfiguration
public class CsrfTests {

private MockMvc mockMvc;

@BeforeEach
public void setUp(WebApplicationContext applicationContext) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
.apply(springSecurity())
.build();
}

@Test
public void loginWhenValidCsrfTokenThenSuccess() throws Exception {
this.mockMvc.perform(post("/login").with(csrf())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, "/"));
}

@Test
public void loginWhenInvalidCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}

@Test
public void loginWhenMissingCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}

@Test
@WithMockUser
public void logoutWhenValidCsrfTokenThenSuccess() throws Exception {
this.mockMvc.perform(post("/logout").with(csrf())
.accept(MediaType.TEXT_HTML))
.andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"));
}
}

禁用 CSRF 保护

默认情况下,CSRF 保护处于启用状态,这会影响与后端的集成以及测试你的应用程序。在禁用 CSRF 保护之前,请考虑它是否适用于你的应用程序

你也可以考虑是否只有某些端点不需要CSRF保护,并配置忽略规则,如下例所示:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.ignoringRequestMatchers("/api/*")
);
return http.build();
}
}

如果你需要禁用 CSRF 保护,可以使用以下配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf.disable());
return http.build();
}
}

CSRF 注意事项

在实现针对CSRF攻击的防护时,有一些特殊的注意事项。本节将讨论这些在Servlet环境中相关的注意事项。更一般的讨论请参见CSRF注意事项

登录

对于登录请求,要求 CSRF 保护至关重要,以防止伪造登录尝试。Spring Security 的 servlet 支持默认已提供此功能。

注销登录

登出请求启用CSRF保护非常重要,这可以防止伪造登出尝试。如果启用了 CSRF 保护(默认启用),Spring Security 的 LogoutFilter 将仅处理 HTTP POST 请求。这确保了登出操作需要 CSRF 令牌,从而防止恶意用户强制注销您的用户。

最简单的方法是使用表单来注销用户。如果你确实想要一个链接,可以使用JavaScript让链接执行POST请求(可能通过隐藏表单实现)。对于禁用JavaScript的浏览器,你可以选择让链接将用户带到一个执行POST请求的注销确认页面。

如果你确实想使用 HTTP GET 方法来实现登出功能,也是可以做到的。但请注意,这种做法通常不被推荐。例如,以下代码会在任何 HTTP 方法请求 /logout 这个 URL 时执行登出操作:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.logout((logout) -> logout
.logoutRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher("/logout"))
);
return http.build();
}
}

更多信息请参见登出章节。

CSRF 与会话超时

默认情况下,Spring Security 使用 HttpSessionCsrfTokenRepository 将 CSRF 令牌存储在 HttpSession 中。这可能导致会话过期后,没有 CSRF 令牌可供验证的情况。

我们已经讨论了会话超时的一般解决方案。本节将讨论与 Servlet 支持相关的 CSRF 超时的具体细节。

您可以将 CSRF 令牌的存储方式更改为存储在 Cookie 中。具体细节请参阅 使用 CookieCsrfTokenRepository 一节。

如果令牌过期,您可能希望通过指定一个自定义的AccessDeniedHandler来自定义其处理方式。自定义的AccessDeniedHandler可以按照您希望的任何方式处理InvalidCsrfTokenException

多部分(文件上传)

我们已经讨论过保护多部分请求(文件上传)免受 CSRF 攻击会导致一个先有鸡还是先有蛋的问题。当 JavaScript 可用时,我们建议将 CSRF 令牌包含在 HTTP 请求头中来规避此问题。

如果 JavaScript 不可用,以下部分将讨论在 servlet 应用程序中将 CSRF 令牌放置在请求体URL中的选项。

备注

关于在 Spring 中使用多部分表单的更多信息,请参阅 Spring 参考文档中的 Multipart Resolver 部分以及 MultipartFilter javadoc

将 CSRF 令牌置于请求体中

我们已经讨论过将 CSRF 令牌放置在请求体中的权衡。在本节中,我们将讨论如何配置 Spring Security 以从请求体中读取 CSRF 令牌。

要从请求体中读取CSRF令牌,需将MultipartFilter配置在Spring Security过滤器之前。将MultipartFilter置于Spring Security过滤器之前意味着调用MultipartFilter时无需授权验证,这可能导致任何用户都能在服务器上放置临时文件。但只有经过授权的用户才能提交由应用程序处理的文件。通常建议采用这种方式,因为临时文件上传对大多数服务器的影响可以忽略不计。

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
备注

为确保在使用 XML 配置时,MultipartFilter 被指定在 Spring Security 过滤器之前,你可以确保在 web.xml 文件中,MultipartFilter<filter-mapping> 元素放置在 springSecurityFilterChain 之前。

在URL中包含CSRF令牌

如果不允许未经授权的用户上传临时文件,另一种方法是将 MultipartFilter 放置在 Spring Security 过滤器之后,并在表单的 action 属性中将 CSRF 令牌作为查询参数包含。由于 CsrfToken 会以 名为 _csrf 的 HttpServletRequest 属性 形式暴露,我们可以利用它来创建一个包含 CSRF 令牌的 action。以下示例通过 JSP 实现了这一做法:

<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">

HiddenHttpMethodFilter

我们已经讨论过将 CSRF 令牌放在请求体中的权衡。

在Spring的Servlet支持中,通过使用HiddenHttpMethodFilter来实现HTTP方法的覆盖。你可以在参考文档的HTTP方法转换部分找到更多信息。

延伸阅读

既然你已经回顾了CSRF防护,可以考虑进一步了解漏洞防护,包括安全标头HTTP防火墙,或者继续学习如何测试你的应用程序。