Cross Site Request Forgery (CSRF)

在最终用户可以 log in的应用程序中,重要的是考虑如何防御 Cross Site Request Forgery (CSRF)

In an application where end users can log in, it is important to consider how to protect against Cross Site Request Forgery (CSRF).

Spring Security 默认情况下会对针对 unsafe HTTP methods的 CSRF 攻击(如 POST 请求)提供保护,因此无需额外的代码。您可以使用以下内容明确指定默认配置:

Spring Security protects against CSRF attacks by default for unsafe HTTP methods, such as a POST request, so no additional code is necessary. You can specify the default configuration explicitly using the following:

Configure CSRF Protection
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf(Customizer.withDefaults());
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf { }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf/>
</http>

要详细了解应用程序的 CSRF 防御,请考虑以下使用案例:

To learn more about CSRF protection for your application, consider the following use cases:

Understanding CSRF Protection’s Components

CSRF 保护由 {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[CsrfFilter] 中组合的多个组件提供:

CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[CsrfFilter]:

csrf
Figure 1. CsrfFilter Components

CSRF 防御分为两部分:

CSRF protection is divided into two parts:

  1. Make the {security-api-url}org/springframework/security/web/csrf/CsrfToken.html[CsrfToken] available to the application by delegating to the <<`CsrfTokenRequestHandler`,csrf-token-request-handler>>.

  2. Determine if the request requires CSRF protection, load and validate the token, and csrf-access-denied-handler.

csrf processing
Figure 2. CsrfFilter Processing
  • number 1 First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[DeferredCsrfToken] is loaded, which holds a reference to the <<`CsrfTokenRepository`,csrf-token-repository>> so that the persisted CsrfToken can be loaded later (in number 4).

  • number 2 Second, a Supplier<CsrfToken> (created from DeferredCsrfToken) is given to the <<`CsrfTokenRequestHandler`,csrf-token-request-handler>>, which is responsible for populating a request attribute to make the CsrfToken available to the rest of the application.

  • number 3 Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends.

  • number 4 If CSRF protection is required, the persisted CsrfToken is finally loaded from the DeferredCsrfToken.

  • number 5 Continuing, the actual CSRF token provided by the client (if any) is resolved using the <<`CsrfTokenRequestHandler`,csrf-token-request-handler>>.

  • number 6 The actual CSRF token is compared against the persisted CsrfToken. If valid, the filter chain is continued and processing ends.

  • number 7 If the actual CSRF token is invalid (or missing), an AccessDeniedException is passed to the <<`AccessDeniedHandler`,csrf-access-denied-handler>> and processing ends.

Migrating to Spring Security 6

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

When migrating from Spring Security 5 to 6, there are a few changes that may impact your application. The following is an overview of the aspects of CSRF protection that have changed in Spring Security 6:

Spring Security 6 中的更改需要对单页应用程序进行额外的配置,因此您可能觉得 Single-Page Applications 一节特别有用。

The changes in Spring Security 6 require additional configuration for single-page applications, and as such you may find the Single-Page Applications section particularly useful.

有关迁移 Spring Security 5 应用程序的更多信息,请参阅 Migration 一章的 Exploit Protection 部分。

See the Exploit Protection section of the Migration chapter for more information on migrating a Spring Security 5 application.

Persisting the CsrfToken

CsrfToken 使用 CsrfTokenRepository 进行持久化。

The CsrfToken is persisted using a CsrfTokenRepository.

默认情况下,<<`HttpSessionCsrfTokenRepository`,csrf-token-repository-httpsession>> 用于将标记存储在会话中。Spring Security 还提供 <<`CookieCsrfTokenRepository`,csrf-token-repository-cookie>> 用于将标记存储在 Cookie 中。您还可以指定 your own implementation 以便根据需要随时存储标记。

By default, the <<`HttpSessionCsrfTokenRepository`,csrf-token-repository-httpsession>> is used for storing tokens in a session. Spring Security also provides the <<`CookieCsrfTokenRepository`,csrf-token-repository-cookie>> for storing tokens in a cookie. You can also specify csrf-token-repository-custom to store tokens wherever you like.

Using the HttpSessionCsrfTokenRepository

默认情况下,Spring Security 使用 {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[HttpSessionCsrfTokenRepository] 将预期的 CSRF 令牌存储在 HttpSession 中,因此无需其他代码。

By default, Spring Security stores the expected CSRF token in the HttpSession by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[HttpSessionCsrfTokenRepository], so no additional code is necessary.

HttpSessionCsrfTokenRepository 默认情况下从名为 X-CSRF-TOKEN 的 HTTP 请求标头或请求参数 _csrf 读入标记。

The HttpSessionCsrfTokenRepository reads the token from an HTTP request header named X-CSRF-TOKEN or the request parameter _csrf by default.

您可以使用以下配置明确指定默认配置:

You can specify the default configuration explicitly using the following configuration:

Configure HttpSessionCsrfTokenRepository
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRepository = HttpSessionCsrfTokenRepository()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository"/>

您可以使用 {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[CookieCsrfTokenRepository] 将 CsrfToken 保存在 cookie 中以 support a JavaScript-based application

You can persist the CsrfToken in a cookie to csrf-integration-javascript using the {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[CookieCsrfTokenRepository].

CookieCsrfTokenRepository 默认情况下会写入一个名为 XSRF-TOKEN 的 cookie,并从一个名为 X-XSRF-TOKEN 的 HTTP 请求头或一个请求参数 _csrf 中读取它。这些默认值来自 Angular 及其前身 AngularJS

The CookieCsrfTokenRepository writes to a cookie named XSRF-TOKEN and reads it from an HTTP request header named X-XSRF-TOKEN or the request parameter _csrf by default. These defaults come from Angular and its predecessor AngularJS.

有关此主题的最新信息,请参阅 Cross-Site Request Forgery (XSRF) protection 指南和 HttpClientXsrfModule

See the Cross-Site Request Forgery (XSRF) protection guide and the HttpClientXsrfModule for more recent information on this topic.

您可以使用以下配置来配置 CookieCsrfTokenRepository

You can configure the CookieCsrfTokenRepository using the following configuration:

Configure CookieCsrfTokenRepository
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>

该示例明确将 HttpOnly 设置为 false。这是必要的,可让 JavaScript 框架(例如 Angular)读入该设置。如果您不需要使用 JavaScript 直接读取 Cookie,我们 recommend 会省略 HttpOnly(改为使用 new CookieCsrfTokenRepository() )以提高安全性。

The example explicitly sets HttpOnly to false. This is necessary to let JavaScript frameworks (such as Angular) read it. If you do not need the ability to read the cookie with JavaScript directly, we recommend omitting HttpOnly (by using new CookieCsrfTokenRepository() instead) to improve security.

Customizing the CsrfTokenRepository

在某些情况下,您可能需要实现一个自定义 {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[CsrfTokenRepository]。

There can be cases where you want to implement a custom {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[CsrfTokenRepository].

一旦实现了 CsrfTokenRepository 接口,您可以将 Spring Security 配置为使用以下配置:

Once you’ve implemented the CsrfTokenRepository interface, you can configure Spring Security to use it with the following configuration:

Configure Custom CsrfTokenRepository
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRepository(new CustomCsrfTokenRepository())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRepository = CustomCsrfTokenRepository()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="example.CustomCsrfTokenRepository"/>

Handling the CsrfToken

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

The CsrfToken is made available to an application using a CsrfTokenRequestHandler. This component is also responsible for resolving the CsrfToken from HTTP headers or request parameters.

默认情况下,<<`XorCsrfTokenRequestAttributeHandler`,csrf-token-request-handler-breach>> 用于提供 CsrfTokenBREACH 保护。Spring Security 还提供了 csrf-token-request-handler-plain 来选择退出 BREACH 保护。您还可以指定 your own implementation 来自定义处理和解析令牌的策略。

By default, the <<`XorCsrfTokenRequestAttributeHandler`,csrf-token-request-handler-breach>> is used for providing BREACH protection of the CsrfToken. Spring Security also provides the <<`CsrfTokenRequestAttributeHandler`,csrf-token-request-handler-plain>> for opting out of BREACH protection. You can also specify csrf-token-request-handler-custom to customize the strategy for handling and resolving tokens.

Using the XorCsrfTokenRequestAttributeHandler (BREACH)

XorCsrfTokenRequestAttributeHandler 使 CsrfToken 可用,作为一个名为 _csrfHttpServletRequest 属性,并为 BREACH 提供保护。

The XorCsrfTokenRequestAttributeHandler makes the CsrfToken available as an HttpServletRequest attribute called _csrf, and additionally provides protection for BREACH.

CsrfToken 还可使用名称 CsrfToken.class.getName() 作为请求属性提供。此名称不可配置,但可以使用 XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName 更改名称 _csrf

The CsrfToken is also made available as a request attribute using the name CsrfToken.class.getName(). This name is not configurable, but the name _csrf can be changed using XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName.

此实现还将标记值从请求中解析为请求标头(默认情况下为 <<`X-CSRF-TOKEN`, csrf-token-repository-httpsession>> 或 <<`X-XSRF-TOKEN`, csrf-token-repository-cookie>>)或请求参数(默认情况下为 _csrf)。

This implementation also resolves the token value from the request as either a request header (one of <<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> or <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>> by default) or a request parameter (_csrf by default).

BREACH 保护通过将随机性编码到 CSRF 令牌值中来确保返回的 CsrfToken 在每个请求中发生更改。当稍后将令牌解析为标头值或请求参数时,它会被解码以获取原始令牌,然后将其与 persisted CsrfToken 进行比较。

BREACH protection is provided by encoding randomness into the CSRF token value to ensure the returned CsrfToken changes on every request. When the token is later resolved as a header value or request parameter, it is decoded to obtain the raw token which is then compared to the csrf-token-repository.

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

Spring Security protects the CSRF token from a BREACH attack by default, so no additional code is necessary. You can specify the default configuration explicitly using the following configuration:

Configure BREACH protection
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>

Using the CsrfTokenRequestAttributeHandler

CsrfTokenRequestAttributeHandler 使 CsrfToken 作为名为 _csrfHttpServletRequest 属性提供。

The CsrfTokenRequestAttributeHandler makes the CsrfToken available as an HttpServletRequest attribute called _csrf.

CsrfToken 还可使用名称 CsrfToken.class.getName() 作为请求属性提供。此名称不可配置,但可以使用 CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName 更改名称 _csrf

The CsrfToken is also made available as a request attribute using the name CsrfToken.class.getName(). This name is not configurable, but the name _csrf can be changed using CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName.

此实现还将标记值从请求中解析为请求标头(默认情况下为 <<`X-CSRF-TOKEN`, csrf-token-repository-httpsession>> 或 <<`X-XSRF-TOKEN`, csrf-token-repository-cookie>>)或请求参数(默认情况下为 _csrf)。

This implementation also resolves the token value from the request as either a request header (one of <<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> or <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>> by default) or a request parameter (_csrf by default).

CsrfTokenRequestAttributeHandler 的主要用途是退出 CsrfToken 的 BREACH 保护,可以使用以下配置进行配置:

The primary use of CsrfTokenRequestAttributeHandler is to opt-out of BREACH protection of the CsrfToken, which can be configured using the following configuration:

Opt-out of BREACH protection
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>

Customizing the CsrfTokenRequestHandler

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

You can implement the CsrfTokenRequestHandler interface to customize the strategy for handling and resolving tokens.

CsrfTokenRequestHandler 接口是 @FunctionalInterface,可以使用 lambda 表达式进行实现以自定义请求处理。您需要实现完整的接口以自定义从请求中解析令牌的方式。请参阅 [csrf-integration-javascript-spa-configuration],其中使用委派来实现处理和解析令牌的自定义策略。

The CsrfTokenRequestHandler interface is a @FunctionalInterface that can be implemented using a lambda expression to customize request handling. You will need to implement the full interface to customize how tokens are resolved from the request. See [csrf-integration-javascript-spa-configuration] for an example that uses delegation to implement a custom strategy for handling and resolving tokens.

一旦实现了 CsrfTokenRequestHandler 接口,您可以将 Spring Security 配置为使用以下配置:

Once you’ve implemented the CsrfTokenRequestHandler interface, you can configure Spring Security to use it with the following configuration:

Configure Custom CsrfTokenRequestHandler
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler())
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRequestHandler = CustomCsrfTokenRequestHandler()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="example.CustomCsrfTokenRequestHandler"/>

Deferred Loading of the CsrfToken

默认情况下,Spring Security 会延迟加载 CsrfToken,直至需要为止。

By default, Spring Security defers loading of the CsrfToken until it is needed.

无论何时通过 unsafe HTTP method(如 POST)进行请求,都需要 CsrfToken。此外,任何向响应中呈现令牌的请求都需要它,例如带有 `<form>`标记并包含用于 CSRF 令牌的隐藏 `<input>`的网页。

The CsrfToken is needed whenever a request is made with an unsafe HTTP method, such as a POST. Additionally, it is needed by any request that renders the token to the response, such as a web page with a <form> tag that includes a hidden <input> for the CSRF token.

由于 Spring Security 默认情况下还将 CsrfToken 存储在 HttpSession 中,因此延迟 CSRF 令牌可以通过不需要在每个请求中加载会话来提高性能。

Because Spring Security also stores the CsrfToken in the HttpSession by default, deferred CSRF tokens can improve performance by not requiring the session to be loaded on every request.

如果您希望选择退出延迟令牌并导致 CsrfToken 在每个请求中加载,可以使用以下配置:

In the event that you want to opt-out of deferred tokens and cause the CsrfToken to be loaded on every request, you can do so with the following configuration:

Opt-out of Deferred CSRF Tokens
  • Java

  • Kotlin

  • XML

@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();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        val requestHandler = XorCsrfTokenRequestAttributeHandler()
        // set the name of the attribute the CsrfToken will be populated on
        requestHandler.setCsrfRequestAttributeName(null)
        http {
            // ...
            csrf {
                csrfTokenRequestHandler = requestHandler
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
	<b:property name="csrfRequestAttributeName">
		<b:null/>
	</b:property>
</b:bean>

通过将 csrfRequestAttributeName 设置为 null,必须首先加载 CsrfToken 以确定要使用的属性名称。这会导致 CsrfToken 在每个请求中加载。

By setting the csrfRequestAttributeName to null, the CsrfToken must first be loaded to determine what attribute name to use. This causes the CsrfToken to be loaded on every request.

Integrating with CSRF Protection

为了使 synchronizer token pattern免受 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。这必须包含在请求的一部分(表单参数、HTTP 头或其他部分)中,浏览器不会自动将它包含在 HTTP 请求中。

For the synchronizer token pattern to protect against CSRF attacks, we must include the actual CSRF token in the HTTP request. This must be included in a part of the request (a form parameter, an HTTP header, or other part) that is not automatically included in the HTTP request by the browser.

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

The following sections describe the various ways a frontend or client application can integrate with a CSRF-protected backend application:

HTML Forms

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

To submit an HTML form, the CSRF token must be included in the form as a hidden input. For example, the rendered HTML might look like:

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

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

The following view technologies automatically include the actual CSRF token in a form that has an unsafe HTTP method, such as a POST:

  • Spring’s form tag library

  • Thymeleaf

  • Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[RequestDataValueProcessor] (via {security-api-url}org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[CsrfRequestDataValueProcessor])

  • You can also include the token yourself via the csrfInput tag

如果这些选项不可用,您可以利用以下事实:SecurityContextHolder 作为名为 csrf-token-request-handlerHttpServletRequest 属性公开。以下示例通过 JSP 这样做:

If these options are not available, you can take advantage of the fact that the CsrfToken is exposed as an <<`HttpServletRequest` attribute named _csrf,csrf-token-request-handler>>. The following example does this with a JSP:

CSRF Token in HTML Form with Request Attribute
<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 Applications

JavaScript 应用程序通常使用 JSON 而不是 HTML。如果您使用 JSON,您可以通过 HTTP 请求头(而不是请求参数)提交 CSRF 令牌。

JavaScript applications typically use JSON instead of HTML. If you use JSON, you can submit the CSRF token within an HTTP request header instead of a request parameter.

为了获得 CSRF 令牌,您可以配置 Spring Security 来存储预期的 CSRF 令牌。通过将预期令牌存储在 cookie 中,JavaScript 框架(例如 jQuery)可以自动将实际 CSRF 令牌作为 HTTP 请求头包含进来。

In order to obtain the CSRF token, you can configure Spring Security to store the expected CSRF token csrf-token-repository-cookie. By storing the expected token in a cookie, JavaScript frameworks such as Angular can automatically include the actual CSRF token as an HTTP request header.

当将单页面应用程序 (SPA) 与 Spring Security 的 CSRF 保护集成时,需要特别考虑 BREACH 保护和延迟令牌。在中提供了完整的配置示例。

There are special considerations for BREACH protection and deferred tokens when integrating a single-page application (SPA) with Spring Security’s CSRF protection. A full configuration example is provided in the csrf-integration-javascript-spa.

您可以在以下部分中了解不同类型的 JavaScript 应用程序:

You can read about different types of JavaScript applications in the following sections:

Single-Page Applications

将单页面应用程序 (SPA) 与 Spring Security 的 CSRF 保护集成时需要特别考虑。

There are special considerations for integrating a single-page application (SPA) with Spring Security’s CSRF protection.

请记住,Spring Security 默认提供 CSRF 保护。当存储预期的 CSRF 令牌时,JavaScript 应用程序只能访问纯令牌值,而不能访问编码值。需要提供一个解析实际令牌值的解析器。

Recall that Spring Security provides csrf-token-request-handler-breach by default. When storing the expected CSRF token csrf-token-repository-cookie, JavaScript applications will only have access to the plain token value and will not have access to the encoded value. A csrf-token-request-handler-custom for resolving the actual token value will need to be provided.

此外,存储 CSRF 令牌的 cookie 将在身份验证成功和注销成功后被清除。Spring Security 默认延迟加载新的 CSRF 令牌,并且需要额外的操作来返回一个新的 cookie。

In addition, the cookie storing the CSRF token will be cleared upon authentication success and logout success. Spring Security defers loading a new CSRF token by default, and additional work is required to return a fresh cookie.

在身份验证成功和注销成功后刷新令牌是必需的,因为 org.springframework.security.web.csrf.CsrfAuthenticationStrategyorg.springframework.security.web.csrf.CsrfLogoutHandler 将清除之前的令牌。客户端应用程序将无法执行不安全 HTTP 请求(例如 POST)而不获取新的令牌。

Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[CsrfAuthenticationStrategy] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[CsrfLogoutHandler] will clear the previous token. The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.

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

In order to easily integrate a single-page application with Spring Security, the following configuration can be used:

Configure CSRF for Single-Page Application
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())   (1)
				.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())            (2)
			)
			.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); (3)
		return http.build();
	}
}

final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
	private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
		/*
		 * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
		 * the CsrfToken when it is rendered in the response body.
		 */
		this.delegate.handle(request, response, csrfToken);
	}

	@Override
	public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
		/*
		 * If the request contains a request header, use CsrfTokenRequestAttributeHandler
		 * to resolve the CsrfToken. This applies when a single-page application includes
		 * the header value automatically, which was obtained via a cookie containing the
		 * raw CsrfToken.
		 */
		if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
			return super.resolveCsrfTokenValue(request, csrfToken);
		}
		/*
		 * In all other cases (e.g. if the request contains a request parameter), use
		 * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
		 * when a server-side rendered form includes the _csrf request parameter as a
		 * hidden input.
		 */
		return this.delegate.resolveCsrfTokenValue(request, csrfToken);
	}
}

final class CsrfCookieFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.getToken();

		filterChain.doFilter(request, response);
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()    (1)
                csrfTokenRequestHandler = SpaCsrfTokenRequestHandler()                 (2)
            }
        }
        http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) (3)
        return http.build()
    }
}

class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
    private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()

    override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
        /*
         * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
         * the CsrfToken when it is rendered in the response body.
         */
        delegate.handle(request, response, csrfToken)
    }

    override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String {
        /*
         * If the request contains a request header, use CsrfTokenRequestAttributeHandler
         * to resolve the CsrfToken. This applies when a single-page application includes
         * the header value automatically, which was obtained via a cookie containing the
         * raw CsrfToken.
         */
        return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) {
            super.resolveCsrfTokenValue(request, csrfToken)
        } else {
            /*
             * In all other cases (e.g. if the request contains a request parameter), use
             * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
             * when a server-side rendered form includes the _csrf request parameter as a
             * hidden input.
             */
            delegate.resolveCsrfTokenValue(request, csrfToken)
        }
    }
}

class CsrfCookieFilter : OncePerRequestFilter() {

    @Throws(ServletException::class, IOException::class)
    override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
        val csrfToken = request.getAttribute("_csrf") as CsrfToken
        // Render the token value to a cookie by causing the deferred token to be loaded
        csrfToken.token
        filterChain.doFilter(request, response)
    }
}
<http>
	<!-- ... -->
	<csrf
		token-repository-ref="tokenRepository"                        1
		request-handler-ref="requestHandler"/>                        2
	<custom-filter ref="csrfCookieFilter" after="BASIC_AUTH_FILTER"/> 3
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>
<b:bean id="requestHandler"
	class="example.SpaCsrfTokenRequestHandler"/>
<b:bean id="csrfCookieFilter"
	class="example.CsrfCookieFilter"/>
1 Configure CookieCsrfTokenRepository with HttpOnly set to false so the cookie can be read by the JavaScript application.
2 Configure a custom CsrfTokenRequestHandler that resolves the CSRF token based on whether it is an HTTP request header (X-XSRF-TOKEN) or request parameter (_csrf).
3 Configure a custom Filter to load the CsrfToken on every request, which will return a new cookie if needed.

Multi-Page Applications

对于在每个页面加载 JavaScript 的多页面应用程序,除了公开 CSRF 令牌之外的另一种选择是在 meta 标签中包含 CSRF 令牌。HTML 可能如下所示:

For multi-page applications where JavaScript is loaded on each page, an alternative to exposing the CSRF token csrf-token-repository-cookie is to include the CSRF token within your meta tags. The HTML might look something like this:

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

为了在请求中包含 CSRF 令牌,您可以利用以下事实:SecurityContextHolder 作为名为 csrf-token-request-handlerHttpServletRequest 属性公开。以下示例通过 JSP 这样做:

In order to include the CSRF token in the request, you can take advantage of the fact that the CsrfToken is exposed as an <<`HttpServletRequest` attribute named _csrf,csrf-token-request-handler>>. The following example does this with a JSP:

CSRF Token in HTML Meta Tag with Request Attribute
<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,您可以使用以下代码这样做:

Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header. If you use jQuery, you can do this with the following code:

Include CSRF Token in AJAX Request
$(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);
	});
});

Other JavaScript Applications

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

Another option for JavaScript applications is to include the CSRF token in an HTTP response header.

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

One way to achieve this is through the use of a @ControllerAdvice with the CsrfTokenArgumentResolver. The following is an example of @ControllerAdvice that applies to all controller endpoints in the application:

CSRF Token in HTTP Response Header
  • Java

  • Kotlin

@ControllerAdvice
public class CsrfControllerAdvice {

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

}
@ControllerAdvice
class CsrfControllerAdvice {

	@ModelAttribute
	fun getCsrfToken(response: HttpServletResponse, csrfToken: CsrfToken) {
		response.setHeader(csrfToken.headerName, csrfToken.token)
	}

}

因为这个注释适用于应用程序中的所有端点,所以它将导致在每个请求中加载 CSRF 令牌,而当使用 HttpSessionCsrfTokenRepository 时,这可能会抵消 CSRF 保护的优点。但是,当使用 CookieCsrfTokenRepository 时,这通常不是问题。

Because this @ControllerAdvice applies to all endpoints in the application, it will cause the CSRF token to be loaded on every request, which can negate the benefits of deferred-csrf-token when using the <<`HttpSessionCsrfTokenRepository`,csrf-token-repository-httpsession>>. However, this is not usually an issue when using the <<`CookieCsrfTokenRepository`,csrf-token-repository-cookie>>.

请记住,控制器端点和控制器建议在 Spring 安全过滤器链中称为 after。这意味着该 @ControllerAdvice 仅在请求通过过滤器链传递到应用程序时才会被应用。请参阅 single-page applications 的配置,了解一个将过滤器添加到过滤器链以更早访问 HttpServletResponse 的示例。

It is important to remember that controller endpoints and controller advice are called after the Spring Security filter chain. This means that this @ControllerAdvice will only be applied if the request passes through the filter chain to your application. See the configuration for csrf-integration-javascript-spa-configuration for an example of adding a filter to the filter chain for earlier access to the HttpServletResponse.

CSR 令牌现在将可在对任何自定义端点的控制器建议适用的响应头中获取(<<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> 或 <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>>,默认为)。可使用对后端的任何请求从响应中获取令牌,而后续请求可将令牌包含在名称相同的请求头中。

The CSRF token will now be available in a response header (<<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> or <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>> by default) for any custom endpoints the controller advice applies to. Any request to the backend can be used to obtain the token from the response, and a subsequent request can include the token in a request header with the same name.

Mobile Applications

JavaScript applications 一样,移动应用程序通常使用 JSON,而不是 HTML。一个 does not 服务浏览器流量的后端应用程序可能会选择 disable CSRF。在这种情况下,不需要任何其他工作。

Like csrf-integration-javascript, mobile applications typically use JSON instead of HTML. A backend application that does not serve browser traffic may choose to disable-csrf. In that case, no additional work is required.

但是,一个既服务浏览器流量又因此 still requires CSR 保护的后端应用程序可能仍继续存储 CsrfToken in the session,而不是 in a cookie

However, a backend application that also serves browser traffic and therefore still requires CSRF protection may continue to store the CsrfToken csrf-token-repository-httpsession instead of csrf-token-repository-cookie.

在这种情况下,与后端集成的典型模式是公开一个 /csrf 端点,供前端(移动或浏览器客户端)按需请求 CSR 令牌。使用此模式的好处是,CSR 令牌 can continue to be deferred 仅在请求需要 CSR 保护时才需要从会话中加载。使用自定义端点还意味着客户端应用程序可以通过发出明确的请求按需请求生成新令牌(如果需要)。

In this case, a typical pattern for integrating with the backend is to expose a /csrf endpoint to allow the frontend (mobile or browser client) to request a CSRF token on demand. The benefit of using this pattern is that the CSRF token deferred-csrf-token and only needs to be loaded from the session when a request requires CSRF protection. The use of a custom endpoint also means the client application can request that a new token be generated on demand (if necessary) by issuing an explicit request.

此模式可用于需要 CSR 保护的任何类型应用程序,而不仅仅是移动应用程序。虽然在这些情况下通常不需要这种方法,但它是与 CSR 受到保护的后端集成的另一种选择。

This pattern can be used for any type of application that requires CSRF protection, not just mobile applications. While this approach isn’t typically required in those cases, it is another option for integrating with a CSRF-protected backend.

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

The following is an example of the /csrf endpoint that makes use of the CsrfTokenArgumentResolver:

The /csrf endpoint
  • Java

  • Kotlin

@RestController
public class CsrfController {

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

}
@RestController
class CsrfController {

    @GetMapping("/csrf")
    fun csrf(csrfToken: CsrfToken): CsrfToken {
        return csrfToken
    }

}

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

You may consider adding .requestMatchers("/csrf").permitAll() if the endpoint above is required prior to authenticating with the server.

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

This endpoint should be called to obtain a CSRF token when the application is launched or initialized (e.g. at load time), and also after authentication success and logout success.

在身份验证成功和注销成功后刷新令牌是必需的,因为 org.springframework.security.web.csrf.CsrfAuthenticationStrategyorg.springframework.security.web.csrf.CsrfLogoutHandler 将清除之前的令牌。客户端应用程序将无法执行不安全 HTTP 请求(例如 POST)而不获取新的令牌。

Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[CsrfAuthenticationStrategy] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[CsrfLogoutHandler] will clear the previous token. The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.

一旦你获取了 CSR 令牌,你需要将其自身作为 HTTP 请求头(默认为 <<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> 或 <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>> 之一)包含在内。

Once you’ve obtained the CSRF token, you will need to include it as an HTTP request header (one of <<`X-CSRF-TOKEN`,csrf-token-repository-httpsession>> or <<`X-XSRF-TOKEN`,csrf-token-repository-cookie>> by default) yourself.

Handle AccessDeniedException

为了处理诸如 InvalidCsrfTokenException 之类的 AccessDeniedException,你可以配置 Spring Security 以任意方式处理这些异常。例如,你可以使用以下配置来配置自定义的拒绝访问页面:

To handle an AccessDeniedException such as InvalidCsrfTokenException, you can configure Spring Security to handle these exceptions in any way you like. For example, you can configure a custom access denied page using the following configuration:

Configure AccessDeniedHandler
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.exceptionHandling((exceptionHandling) -> exceptionHandling
				.accessDeniedPage("/access-denied")
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            exceptionHandling {
                accessDeniedPage = "/access-denied"
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<access-denied-handler error-page="/access-denied"/>
</http>

CSRF Testing

您可以使用 Spring Security 的 testing supportCsrfRequestPostProcessor 来测试 CSRF 保护,如下所示:

You can use Spring Security’s testing support and CsrfRequestPostProcessor to test CSRF protection, like this:

Test CSRF Protection
  • Java

  • Kotlin

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
	@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"));
	}
}
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*

@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [SecurityConfig::class])
@WebAppConfiguration
class CsrfTests {
	private lateinit var mockMvc: MockMvc

	@BeforeEach
	fun setUp(applicationContext: WebApplicationContext) {
		mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
			.apply<DefaultMockMvcBuilder>(springSecurity())
			.build()
	}

	@Test
	fun loginWhenValidCsrfTokenThenSuccess() {
		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
	@WithMockUser
	@Throws(Exception::class)
	fun logoutWhenValidCsrfTokenThenSuccess() {
		mockMvc.perform(post("/logout").with(csrf())
				.accept(MediaType.TEXT_HTML))
			.andExpect(status().is3xxRedirection)
			.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"))
	}
}

Disable CSRF Protection

默认情况下,启用 CSRF 保护,这会影响应用程序的 integrating with the backendtesting。在禁用 CSRF 保护之前,请考虑是否 makes sense for your application

By default, CSRF protection is enabled, which affects csrf-integration and csrf-testing your application. Before disabling CSRF protection, consider whether it makes sense for your application.

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

You can also consider whether only certain endpoints do not require CSRF protection and configure an ignoring rule, as in the following example:

Ignoring Requests
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // ...
            .csrf((csrf) -> csrf
                .ignoringRequestMatchers("/api/*")
            );
        return http.build();
    }
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                ignoringRequestMatchers("/api/*")
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf request-matcher-ref="csrfMatcher"/>
</http>
<b:bean id="csrfMatcher"
    class="org.springframework.security.web.util.matcher.AndRequestMatcher">
    <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
    <b:constructor-arg>
        <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
            <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
                <b:constructor-arg value="/api/*"/>
            </b:bean>
        </b:bean>
    </b:constructor-arg>
</b:bean>

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

If you need to disable CSRF protection, you can do so using the following configuration:

Disable CSRF
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.csrf((csrf) -> csrf.disable());
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            csrf {
                disable()
            }
        }
        return http.build()
    }
}
<http>
	<!-- ... -->
	<csrf disabled="true"/>
</http>

CSRF Considerations

在实现针对 CSRF 攻击的保护时,有一些特殊注意事项。本节讨论了这些注意事项,因为它们与 servlet 环境有关。有关更一般的讨论,请参阅 CSRF Considerations

There are a few special considerations when implementing protection against CSRF attacks. This section discusses those considerations as they pertain to servlet environments. See CSRF Considerations for a more general discussion.

Logging In

require CSRF for log in请求对于保护伪造登录尝试非常重要。Spring Security 对 servlet 的支持开箱即用地实现了这一点。

It is important to require CSRF for log in requests to protect against forging log in attempts. Spring Security’s servlet support does this out of the box.

Logging Out

require CSRF for log out请求对于防御伪造注销尝试非常重要。如果启用 CSRF 保护(默认),Spring Security 的 `LogoutFilter`将只处理 HTTP POST 请求。这可确保注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。

It is important to require CSRF for log out requests to protect against forging logout attempts. If CSRF protection is enabled (the default), Spring Security’s LogoutFilter will only process HTTP POST requests. This ensures that logging out requires a CSRF token and that a malicious user cannot forcibly log your users out.

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

The easiest approach is to use a form to log the user out. If you really want a link, you can use JavaScript to have the link perform a POST (maybe on a hidden form). For browsers with JavaScript that is disabled, you can optionally have the link take the user to a log out confirmation page that performs the POST.

如果你真的想使用 HTTP GET 注销,你可以这么做。但是,请记住,通常不建议这样做。例如,以下内容会在使用任何 HTTP 方法请求 /logout URL 时注销:

If you really want to use HTTP GET with logout, you can do so. However, remember that this is generally not recommended. For example, the following logs out when the /logout URL is requested with any HTTP method:

Log Out with Any HTTP Method
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.logout((logout) -> logout
				.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
			);
		return http.build();
	}
}
import org.springframework.security.config.annotation.web.invoke

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            logout {
                logoutRequestMatcher = AntPathRequestMatcher("/logout")
            }
        }
        return http.build()
    }
}

有关更多信息,请参阅Logout章节。

See the Logout chapter for more information.

CSRF and Session Timeouts

默认情况下,Spring Security 将 CSRF 令牌存储在`HttpSession`中,使用<<`HttpSessionCsrfTokenRepository`,csrf-token-repository-httpsession>>。这会导致这样一个情况:会话过期,没有 CSRF 令牌进行验证。

By default, Spring Security stores the CSRF token in the HttpSession using the <<`HttpSessionCsrfTokenRepository`,csrf-token-repository-httpsession>>. This can lead to a situation where the session expires, leaving no CSRF token to validate against.

我们在 general solutions 讨论了会话超时。该部分讨论了 CSRF 超时的具体内容,因为它与服务支持有关。

We have already discussed general solutions to session timeouts. This section discusses the specifics of CSRF timeouts as it pertains to the servlet support.

你可以将 CSRF 令牌存储更改为 cookie 中。有关详细信息,请参阅Using the CookieCsrfTokenRepository一节。

You can change the storage of the CSRF token to be in a cookie. For details, see the Using the CookieCsrfTokenRepository section.

如果令牌确实过期,你可能希望通过指定一个custom AccessDeniedHandler来自定义处理方式。自定义`AccessDeniedHandler`可以任意处理`InvalidCsrfTokenException`。

If a token does expire, you might want to customize how it is handled by specifying a csrf-access-denied-handler. The custom AccessDeniedHandler can process the InvalidCsrfTokenException any way you like.

Multipart (file upload)

我们 already discussed 如何保护 multipart 请求(文件上传)免受 CSRF 攻击导致 chicken and the egg 问题。当可以使用 JavaScript 时,我们 recommend including the CSRF token in an HTTP request header 来回避该问题。

We have already discussed how protecting multipart requests (file uploads) from CSRF attacks causes a chicken and the egg problem. When JavaScript is available, we recommend csrf-integration-javascript-other to side-step the issue.

如果未启用 JavaScript,以下部分将讨论将 CSRF 令牌放入 servlet 应用程序中的bodyurl 的选项。

If JavaScript is not available, the following sections discuss options for placing the CSRF token in the csrf-considerations-multipart-body and csrf-considerations-multipart-url within a servlet application.

你可以在 Spring 参考的 Multipart Resolver部分和{spring-framework-api-url}org/springframework/web/multipart/support/MultipartFilter.html[MultipartFilter javadoc]中找到有关在 Spring 中使用 multipart 表格的更多信息。

You can find more information about using multipart forms with Spring in the Multipart Resolver section of the Spring reference and the {spring-framework-api-url}org/springframework/web/multipart/support/MultipartFilter.html[MultipartFilter javadoc].

Place CSRF Token in the Body

我们 already discussed 了将 CSRF 令牌放置在正文中的权衡。在该部分中,我们讨论如何配置 Spring Security 从正文读取 CSRF。

We have already discussed the tradeoffs of placing the CSRF token in the body. In this section, we discuss how to configure Spring Security to read the CSRF from the body.

要从 body 读取 CSRF 令牌,需要在 Spring Security 过滤器之前指定`MultipartFilter`。在 Spring Security 过滤器之前指定`MultipartFilter`意味着没有对调用`MultipartFilter`的授权,这意味着任何人都可以将临时文件放置在你的服务器上。但是,只有授权用户才能提交你的应用程序处理的文件。通常情况下,这是推荐的方法,因为临时文件上传对大多数服务器的影响微乎其微。

To read the CSRF token from the body, the MultipartFilter is specified before the Spring Security filter. Specifying the MultipartFilter before the Spring Security filter means that there is no authorization for invoking the MultipartFilter, which means anyone can place temporary files on your server. However, only authorized users can submit a file that is processed by your application. In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.

Configure MultipartFilter
  • Java

  • Kotlin

  • XML

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

	@Override
	protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
		insertFilters(servletContext, new MultipartFilter());
	}
}
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
    override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
        insertFilters(servletContext, MultipartFilter())
    }
}
<filter>
	<filter-name>MultipartFilter</filter-name>
	<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>MultipartFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

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

To ensure that MultipartFilter is specified before the Spring Security filter with XML configuration, you can ensure the <filter-mapping> element of the MultipartFilter is placed before the springSecurityFilterChain within the web.xml file.

Include a CSRF Token in a URL

如果允许未授权用户上传临时文件不可接受,一种解决方法是将 MultipartFilter 放在 Spring Security 过滤器之后,并将 CSRF 作为查询参数包含在表单的 action 属性中。由于 CsrfToken 以名为 _csrf`的<<`HttpServletRequest`属性的形式公开,csrf-token-request-handler>>, 我们可以使用它来创建一个其中包含 CSRF 令牌的`action。以下示例通过 JSP 来执行此操作:

If letting unauthorized users upload temporary files is not acceptable, an alternative is to place the MultipartFilter after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form. Since the CsrfToken is exposed as an <<`HttpServletRequest` attribute named _csrf,csrf-token-request-handler>>, we can use that to create an action with the CSRF token in it. The following example does this with a JSP:

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

HiddenHttpMethodFilter

我们已经 already discussed 讨论了将 CSRF 令牌放置在正文中的权衡。

We have already discussed the trade-offs of placing the CSRF token in the body.

在 Spring 的 Servlet 支持中,通过使用{spring-framework-api-url}org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[HiddenHttpMethodFilter]来覆盖 HTTP 方法。你可以在参考文档的 HTTP Method Conversion部分找到更多信息。

In Spring’s Servlet support, overriding the HTTP method is done by using {spring-framework-api-url}org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[HiddenHttpMethodFilter]. You can find more information in the HTTP Method Conversion section of the reference documentation.

Further Reading

现在你已经了解了 CSRF 防护,请考虑更多了解exploit protection,包括secure headersHTTP firewall,或者继续了解如何test你的应用。

Now that you have reviewed CSRF protection, consider learning more about exploit protection including secure headers and the HTTP firewall or move on to learning how to test your application.