对于最新的稳定版本,请使用 Spring Security 6.4.1! |
跨站点请求伪造 (CSRF)
在最终用户可以登录的应用程序中,重要的是要考虑如何防止跨站点请求伪造 (CSRF)。
默认情况下, Spring Security 可以防止针对不安全的 HTTP 方法(例如 POST 请求)的 CSRF 攻击,因此不需要额外的代码。 您可以使用以下内容显式指定默认配置:
-
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 保护的更多信息,请考虑以下使用案例:
-
我想选择退出延迟令牌
-
我需要将 Lymeleaf、JSP 或其他视图技术与后端集成的指导
-
我需要将 Angular 或其他 JavaScript 框架与后端集成的指导
-
我需要指导将移动应用程序或其他客户端与后端集成
-
我需要有关处理错误的指导
-
我需要有关禁用 CSRF 保护的指导
了解 CSRF 保护的组件
CSRF 保护由多个组件提供,这些组件组成在CsrfFilter
:

CsrfFilter
组件CSRF 保护分为两部分:
-
使
CsrfToken
available 提供给应用程序,方法是将CsrfTokenRequestHandler
. -
确定请求是否需要 CSRF 保护,加载并验证令牌,以及处理
AccessDeniedException
.

CsrfFilter
加工-
首先,
DeferredCsrfToken
加载了它,它包含对CsrfTokenRepository
这样,持久化的CsrfToken
可以稍后加载 (在中)。
-
其次,一个
Supplier<CsrfToken>
(创建自DeferredCsrfToken
) 被赋予CsrfTokenRequestHandler
,它负责填充 request 属性以使CsrfToken
可用于应用程序的其余部分。 -
接下来,主 CSRF 保护处理开始并检查当前请求是否需要 CSRF 保护。如果不需要,则继续筛选链并结束处理。
-
如果需要 CSRF 保护,则持久化的
CsrfToken
最终从DeferredCsrfToken
. -
继续,客户端提供的实际 CSRF 令牌(如果有)使用
CsrfTokenRequestHandler
. -
将实际的 CSRF 令牌与持久化的 CSRF 令牌进行比较
CsrfToken
.如果有效,则继续筛选链并结束处理。 -
如果实际的 CSRF 令牌无效(或缺失),则
AccessDeniedException
传递给AccessDeniedHandler
和处理结束。
迁移到 Spring Security 6
从 Spring Security 5 迁移到 6 时,有一些更改可能会影响您的应用程序。 以下是 Spring Security 6 中已更改的 CSRF 保护方面的概述:
-
加载
CsrfToken
现在默认延迟,以提高性能,因为不再需要在每个请求上加载会话。 -
这
CsrfToken
现在默认在每个请求中包含随机性,以保护 CSRF 令牌免受 BREACH 攻击。
Spring Security 6 中的更改需要对单页应用程序进行额外配置,因此您可能会发现 Single-Page Applications 部分特别有用。 |
有关迁移 Spring Security 5 应用程序的更多信息,请参见迁移一章的 Exploit Protection 部分。
持久化CsrfToken
这CsrfToken
使用CsrfTokenRepository
.
默认情况下,HttpSessionCsrfTokenRepository
用于在 Session 中存储令牌。
Spring Security 还提供了CookieCsrfTokenRepository
用于在 Cookie 中存储令牌。
您还可以指定自己的 implementation 以将 token 存储在您喜欢的任何位置。
使用HttpSessionCsrfTokenRepository
默认情况下,Spring Security 将预期的 CSRF 令牌存储在HttpSession
通过使用HttpSessionCsrfTokenRepository
,因此不需要其他代码。
这HttpSessionCsrfTokenRepository
从名为X-CSRF-TOKEN
或 request 参数_csrf
默认情况下。
您可以使用以下配置显式指定默认配置:
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"/>
使用CookieCsrfTokenRepository
您可以持久化CsrfToken
以支持基于 JavaScript 的应用程序,请使用CookieCsrfTokenRepository
.
这CookieCsrfTokenRepository
写入名为XSRF-TOKEN
并从名为X-XSRF-TOKEN
或 request 参数_csrf
默认情况下。
这些默认值来自 Angular 及其前身 AngularJS。
有关此主题的最新信息,请参阅跨站点请求伪造 (XSRF) 保护指南和 HttpClientXsrfModule 。 |
您可以配置CookieCsrfTokenRepository
使用以下配置:
该示例显式地将 |
自定义CsrfTokenRepository
在某些情况下,您可能希望实现自定义CsrfTokenRepository
.
实施CsrfTokenRepository
接口,你可以将 Spring Security 配置为通过以下配置来使用它:
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"/>
处理CsrfToken
这CsrfToken
可供应用程序使用CsrfTokenRequestHandler
.
此组件还负责解析CsrfToken
从 HTTP 标头或请求参数。
默认情况下,XorCsrfTokenRequestAttributeHandler
用于提供CsrfToken
.
Spring Security 还提供了CsrfTokenRequestAttributeHandler
选择退出 BREACH 保护。
您还可以指定自己的实现来自定义处理和解析令牌的策略。
使用XorCsrfTokenRequestAttributeHandler
(泄露)
这XorCsrfTokenRequestAttributeHandler
使CsrfToken
以HttpServletRequest
属性调用_csrf
,此外还为 BREACH 提供保护。
这 |
此实现还将请求中的令牌值解析为请求标头(X-CSRF-TOKEN
或X-XSRF-TOKEN
)或请求参数 (_csrf
默认情况下)。
BREACH 保护是通过将随机性编码到 CSRF 令牌值中来提供的,以确保返回的 |
默认情况下, Spring Security 保护 CSRF 令牌免受 BREACH 攻击,因此不需要额外的代码。 您可以使用以下配置显式指定默认配置:
-
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"/>
使用CsrfTokenRequestAttributeHandler
这CsrfTokenRequestAttributeHandler
使CsrfToken
以HttpServletRequest
属性调用_csrf
.
这 |
此实现还将请求中的令牌值解析为请求标头(X-CSRF-TOKEN
或X-XSRF-TOKEN
)或请求参数 (_csrf
默认情况下)。
主要用途CsrfTokenRequestAttributeHandler
是选择退出 BREACH 保护CsrfToken
,可以使用以下配置进行配置:
-
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"/>
自定义CsrfTokenRequestHandler
您可以实现CsrfTokenRequestHandler
接口自定义处理和解析 Token 的策略。
这 |
实施CsrfTokenRequestHandler
接口,你可以将 Spring Security 配置为通过以下配置来使用它:
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 的CsrfToken
默认情况下,Spring Security 会延迟CsrfToken
直到需要为止。
这 |
由于 Spring Security 还会存储CsrfToken
在HttpSession
默认情况下,延迟的 CSRF 令牌可以通过不要求在每个请求上加载 session 来提高性能。
如果您想选择退出延迟令牌,并导致CsrfToken
要在每个请求上加载,您可以使用以下配置进行加载:
-
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>
通过设置 |
与 CSRF 保护集成
为了使同步器令牌模式防止 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。 这必须包含在浏览器不会自动包含在 HTTP 请求中的请求部分(表单参数、HTTP 标头或其他部分)中。
以下部分描述了前端或客户端应用程序与受 CSRF 保护的后端应用程序集成的各种方式:
HTML 表单
要提交 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。 例如,呈现的 HTML 可能如下所示:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
以下视图技术会自动将实际的 CSRF 令牌包含在具有不安全 HTTP 方法的形式中,例如 POST:
-
您还可以通过 csrfInput 标记自己包含令牌
如果这些选项不可用,您可以利用CsrfToken
公开为HttpServletRequest
名为_csrf
.
以下示例使用 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,则可以在 HTTP 请求标头中提交 CSRF 令牌,而不是请求参数。
为了获得 CSRF 令牌,你可以将 Spring Security 配置为将预期的 CSRF 令牌存储在 cookie 中。 通过将预期的令牌存储在 cookie 中,JavaScript 框架(如 Angular)可以自动将实际的 CSRF 令牌作为 HTTP 请求标头包含。
在将单页应用程序 (SPA) 与 Spring Security 的 CSRF 保护集成时,对 BREACH 保护和延迟令牌有一些特殊注意事项。 下一节将提供完整的配置示例。 |
您可以在以下各节中了解不同类型的 JavaScript 应用程序:
单页应用程序
将单页应用程序 (SPA) 与 Spring Security 的 CSRF 保护集成时,有一些特殊的注意事项。
回想一下 Spring Security 提供了BREACH 保护CsrfToken
默认情况下。
在 cookie 中存储预期的 CSRF 令牌时,JavaScript 应用程序将只能访问普通令牌值,而无权访问编码值。
需要提供用于解析实际令牌值的自定义请求处理程序。
此外,在鉴权成功和注销成功时,存储 CSRF Token 的 Cookie 将被清空。 默认情况下, Spring Security 会延迟加载新的 CSRF 令牌,并且需要额外的工作才能返回新的 cookie。
需要在身份验证成功和注销成功后刷新令牌,因为 |
为了轻松地将单页应用程序与 Spring Security 集成,可以使用以下配置:
-
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 | 配置CookieCsrfTokenRepository 跟HttpOnly 设置为false ,以便 JavaScript 应用程序可以读取 Cookie。 |
2 | 配置自定义CsrfTokenRequestHandler ,根据 CSRF 令牌是否为 HTTP 请求标头 (X-XSRF-TOKEN ) 或请求参数 (_csrf ). |
3 | 配置自定义Filter 加载CsrfToken 在每个请求中,如果需要,它将返回一个新的 Cookie。 |
多页应用程序
对于在每个页面上加载 JavaScript 的多页面应用程序,在 cookie 中公开 CSRF 令牌的另一种方法是将 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
公开为HttpServletRequest
名为_csrf
.
以下示例使用 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>
一旦 meta 标记包含 CSRF 令牌,JavaScript 代码就可以读取 meta 标记并将 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 应用程序的另一个选项是在 HTTP 响应标头中包含 CSRF 令牌。
实现此目的的一种方法是使用@ControllerAdvice
使用CsrfTokenArgumentResolver
.
以下是@ControllerAdvice
这适用于应用程序中的所有控制器端点:
-
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)
}
}
因为这个 |
重要的是要记住,控制器端点和控制器建议是在 Spring Security 过滤器链之后调用的。
这意味着 |
CSRF 令牌现在将在响应标头 (X-CSRF-TOKEN
或X-XSRF-TOKEN
默认情况下),对于控制器建议适用的任何自定义终端节点。
对后端的任何请求都可用于从响应中获取令牌,并且后续请求可以将令牌包含在具有相同名称的请求标头中。
移动应用程序
与 JavaScript 应用程序一样,移动应用程序通常使用 JSON 而不是 HTML。 不提供浏览器流量的后端应用程序可以选择禁用 CSRF。 在这种情况下,不需要额外的工作。
但是,如果后端应用程序也提供浏览器流量,因此仍然需要 CSRF 保护,则可以继续存储CsrfToken
在 session 中,而不是在 cookie 中。
在这种情况下,与后端集成的典型模式是公开/csrf
endpoint 允许前端(移动或浏览器客户端)按需请求 CSRF 令牌。
使用这种模式的好处是 CSRF 令牌可以继续延迟,并且仅在请求需要 CSRF 保护时才需要从 session 加载。
使用自定义终端节点还意味着客户端应用程序可以通过发出显式请求来请求按需生成新令牌(如有必要)。
此模式可用于需要 CSRF 保护的任何类型的应用程序,而不仅仅是移动应用程序。 虽然在这些情况下通常不需要这种方法,但它是与受 CSRF 保护的后端集成的另一种选择。 |
以下是/csrf
端点,该端点使用CsrfTokenArgumentResolver
:
/csrf
端点-
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
}
}
您可以考虑添加 |
在启动或初始化应用程序时(例如,在加载时),以及在身份验证成功和注销成功后,应调用此端点以获取 CSRF 令牌。
需要在身份验证成功和注销成功后刷新令牌,因为 |
获得 CSRF 令牌后,您需要将其作为 HTTP 请求标头(以下X-CSRF-TOKEN
或X-XSRF-TOKEN
默认情况下)自己。
处理AccessDeniedException
要处理AccessDeniedException
如InvalidCsrfTokenException
,您可以配置 Spring Security 以您喜欢的任何方式处理这些异常。
例如,您可以使用以下配置配置自定义 access denied 页面:
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 测试
您可以使用 Spring Security 的测试支持和CsrfRequestPostProcessor
测试 CSRF 保护,如下所示:
-
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"))
}
}
禁用 CSRF 保护
默认情况下,CSRF 保护处于启用状态,这会影响与后端的集成和测试您的应用程序。 在禁用 CSRF 保护之前,请考虑它对您的应用程序是否有意义。
您还可以考虑是否只有某些终端节点不需要 CSRF 保护并配置忽略规则,如以下示例所示:
-
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>
如果需要禁用 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 注意事项
在实施针对 CSRF 攻击的保护时,有一些特殊的注意事项。 本节讨论与 Servlet 环境相关的这些注意事项。 有关更一般的讨论,请参阅 CSRF 注意事项。
登录
要求对登录请求使用 CSRF 以防止伪造登录尝试非常重要。 Spring Security 的 servlet 支持是开箱即用的。
注销
对于注销请求,要求 CSRF 以防止伪造注销尝试,这一点很重要。
如果启用了 CSRF 保护(默认),则 Spring Security 的LogoutFilter
将仅处理 HTTP POST 请求。
这可确保注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。
最简单的方法是使用表单将用户注销。 如果你真的想要一个链接,你可以使用 JavaScript 让链接执行 POST(可能在隐藏的表单上)。 对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到执行 POST 的注销确认页面。
如果您确实想将 HTTP GET 与 logout 一起使用,则可以这样做。
但是,请记住,这通常不建议这样做。
例如,当/logout
使用任何 HTTP 方法请求 URL:
-
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 章节。
CSRF 和会话超时
默认情况下,Spring Security 将 CSRF 令牌存储在HttpSession
使用HttpSessionCsrfTokenRepository
.
这可能会导致会话过期,没有留下 CSRF 令牌进行验证。
我们已经讨论了会话超时的一般解决方案。 本节讨论与 Servlet 支持相关的 CSRF 超时的细节。
您可以将 CSRF 令牌的存储更改为在 cookie 中。
有关详细信息,请参阅使用CookieCsrfTokenRepository
部分。
如果令牌确实过期,您可能希望通过指定习惯AccessDeniedHandler
.
自定义AccessDeniedHandler
可以处理InvalidCsrfTokenException
随便你怎么。
分段(文件上传)
我们已经讨论了保护分段请求(文件上传)免受 CSRF 攻击如何导致先有鸡还是先有蛋的问题。 当 JavaScript 可用时,我们建议在 HTTP 请求标头中包含 CSRF 令牌,以避免该问题。
您可以在 Spring 参考的 Multipart Resolver 部分中找到有关在 Spring 中使用多部分表单的更多信息,以及 |
将 CSRF 令牌放在正文中
我们已经讨论了将 CSRF 令牌放在 body 中的权衡。 在本节中,我们将讨论如何配置 Spring Security 以从正文中读取 CSRF。
要从正文中读取 CSRF 令牌,MultipartFilter
在 Spring Security 过滤器之前指定。
指定MultipartFilter
在 Spring Security 过滤器之前,意味着没有调用MultipartFilter
,这意味着任何人都可以在您的服务器上放置临时文件。
但是,只有授权用户才能提交由您的应用程序处理的文件。
通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响应该可以忽略不计。
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>
要确保 |
在 URL 中包含 CSRF 令牌
如果不允许未经授权的用户上传临时文件,另一种方法是将MultipartFilter
在 Spring Security 过滤器之后,并将 CSRF 作为查询参数包含在表单的 action 属性中。
由于CsrfToken
公开为HttpServletRequest
名为_csrf
,我们可以使用它来创建一个action
其中包含 CSRF 令牌。
以下示例使用 JSP 执行此作:
<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">
隐藏的HttpMethodFilter
我们已经讨论了将 CSRF 令牌放入 body 中的权衡。
在 Spring 的 Servlet 支持中,覆盖 HTTP 方法是通过使用HiddenHttpMethodFilter
.
您可以在参考文档的 HTTP 方法转换部分找到更多信息。