跨站点请求伪造 (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 保护的更多信息,请考虑以下用例:
-
我需要将 Thymeleaf、JSP 或其他视图技术与后端集成的指导
-
我需要将 Angular 或其他 JavaScript 框架与后端集成的指导
-
我需要将移动应用程序或其他客户端与后端集成的指导
-
我需要有关处理错误的指导
-
我需要有关禁用 CSRF 保护的指导
了解 CSRF 保护的组件
CSRF 保护由多个组件提供,这些组件组成在CsrfFilter
:

CsrfFilter
组件CSRF 保护分为两部分:
-
确定请求是否需要 CSRF 保护,加载并验证Tokens,以及处理
AccessDeniedException
.

CsrfFilter
加工-
首先,
DeferredCsrfToken
加载,其中包含对CsrfTokenRepository
这样CsrfToken
可以稍后加载(在中)。
-
第二,一个
Supplier<CsrfToken>
(创建自DeferredCsrfToken
) 被赋予CsrfTokenRequestHandler
,它负责填充请求属性以使CsrfToken
可用于应用程序的其余部分。 -
接下来,开始主 CSRF 保护处理并检查当前请求是否需要 CSRF 保护。如果不需要,则继续过滤链并结束处理。
-
如果需要 CSRF 保护,则持久化的
CsrfToken
最终从DeferredCsrfToken
. -
继续,客户端提供的实际 CSRF Tokens(如果有)将使用
CsrfTokenRequestHandler
. -
将实际的 CSRF Tokens与持久化的
CsrfToken
.如果有效,则继续筛选链并结束处理。 -
如果实际的 CSRF Tokens无效(或丢失),则
AccessDeniedException
传递给AccessDeniedHandler
和处理结束。
持久化CsrfToken
这CsrfToken
使用CsrfTokenRepository
.
默认情况下,HttpSessionCsrfTokenRepository
用于在会话中存储Tokens。
Spring Security 还提供了CookieCsrfTokenRepository
用于将Tokens存储在 cookie 中。
您还可以指定自己的实现,以将Tokens存储在您喜欢的任何位置。
使用HttpSessionCsrfTokenRepository
默认情况下,Spring Security 将预期的 CSRF Tokens存储在HttpSession
通过使用HttpSessionCsrfTokenRepository
,因此不需要额外的代码。
这HttpSessionCsrfTokenRepository
从会话(无论是内存中、缓存还是数据库)读取Tokens。如果需要直接访问会话属性,请先使用HttpSessionCsrfTokenRepository#setSessionAttributeName
.
您可以使用以下配置显式指定默认配置:
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
在 cookie 中支持基于 JavaScript 的应用程序,使用CookieCsrfTokenRepository
.
这CookieCsrfTokenRepository
写入名为XSRF-TOKEN
并从名为X-XSRF-TOKEN
或请求参数_csrf
默认情况下。
这些默认值来自 Angular 及其前身 AngularJS。
有关此主题的更多最新信息,请参阅 HttpClient XSRF/CSRF 安全性和 withXsrfConfiguration。 |
您可以配置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
用于提供 BREACH 保护CsrfToken
.
Spring Security 还提供了CsrfTokenRequestAttributeHandler
选择退出 BREACH 保护。
您还可以指定自己的实现来自定义处理和解析Tokens的策略。
使用XorCsrfTokenRequestAttributeHandler
(违规)
这XorCsrfTokenRequestAttributeHandler
使CsrfToken
可作为HttpServletRequest
属性调用_csrf
,并额外提供对 BREACH 的保护。
这 |
此实现还将请求中的Tokens值解析为请求标头(X-CSRF-TOKEN
或X-XSRF-TOKEN
默认情况下)或请求参数(_csrf
默认情况下)。
BREACH 保护是通过将随机性编码到 CSRF Tokens值中来提供的,以确保返回的 |
默认情况下,Spring Security 保护 CSRF Tokens免受 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
.
这 |
此实现还将请求中的Tokens值解析为请求标头(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
界面来自定义处理和解析Tokens的策略。
这 |
一旦你实现了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"/>
延迟加载CsrfToken
默认情况下,Spring Security 会延迟加载CsrfToken
直到需要为止。
这 |
因为 Spring Security 还存储了CsrfToken
在HttpSession
默认情况下,延迟的 CSRF Tokens可以通过不要求在每个请求上加载会话来提高性能。
如果您想选择退出延迟Tokens并导致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 保护集成
为了使同步器Tokens模式防范 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF Tokens。 这必须包含在浏览器不会自动包含在 HTTP 请求中的请求的一部分(表单参数、HTTP 标头或其他部分)中。
以下部分介绍了前端或客户端应用程序与受 CSRF 保护的后端应用程序集成的各种方式:
HTML 表单
要提交 HTML 表单,必须将 CSRF Tokens作为隐藏输入包含在表单中。 例如,呈现的 HTML 可能如下所示:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
以下视图技术会自动将实际的 CSRF Tokens包含在具有不安全 HTTP 方法(例如 POST)的形式中:
-
您也可以通过 csrfInput 标记自行包含Tokens
如果这些选项不可用,您可以利用以下事实: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 Tokens。
为了获取 CSRF Tokens,您可以将 Spring Security 配置为将预期的 CSRF Tokens存储在 cookie 中。 通过将预期的Tokens存储在 cookie 中,Angular 等 JavaScript 框架可以自动将实际的 CSRF Tokens作为 HTTP 请求标头包含在内。
在将单页应用程序(SPA)与Spring Security的CSRF保护集成时,BREACH保护和延迟Tokens需要特殊注意事项。 下一节将提供完整的配置示例。 |
您可以在以下部分中阅读不同类型的 JavaScript 应用程序:
单页应用程序
将单页应用程序(SPA)与Spring Security的CSRF保护集成时,需要注意事项。
回想一下,Spring Security 提供了BREACH 保护CsrfToken
默认情况下。
将预期的 CSRF Tokens存储在 cookie 中时,JavaScript 应用程序只能访问普通Tokens值,而无法访问编码值。
需要提供用于解析实际Tokens值的自定义请求处理程序。
此外,存储 CSRF Tokens的 cookie 将在身份验证成功和注销成功时被清除。 默认情况下,Spring Security 会延迟加载新的 CSRF Tokens,并且需要额外的工作才能返回新的 cookie。
需要在身份验证成功和注销成功后刷新Tokens,因为 |
为了轻松地将单页应用程序与 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)
);
return http.build();
}
}
final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
private final CsrfTokenRequestHandler xor = 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.xor.handle(request, response, csrfToken);
/*
* Render the token value to a cookie by causing the deferred token to be loaded.
*/
csrfToken.get();
}
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
String headerValue = request.getHeader(csrfToken.getHeaderName());
/*
* 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.
*
* 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 (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
}
}
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)
}
}
return http.build()
}
}
class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler {
private val plain: CsrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
private val xor: 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.
*/
xor.handle(request, response, csrfToken)
/*
* Render the token value to a cookie by causing the deferred token to be loaded.
*/
csrfToken.get()
}
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
val headerValue = request.getHeader(csrfToken.headerName)
/*
* 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(headerValue)) {
plain
} 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.
*/
xor
}.resolveCsrfTokenValue(request, csrfToken)
}
}
<http>
<!-- ... -->
<csrf
token-repository-ref="tokenRepository" (1)
request-handler-ref="requestHandler"/> (2)
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
<b:bean id="requestHandler"
class="example.SpaCsrfTokenRequestHandler"/>
1 | 配置CookieCsrfTokenRepository 跟HttpOnly 设置为false 因此 JavaScript 应用程序可以读取 cookie。 |
2 | 配置自定义CsrfTokenRequestHandler 根据 CSRF Tokens是否是 HTTP 请求标头 (X-XSRF-TOKEN ) 或请求参数 (_csrf ).
此实现还会导致延迟的CsrfToken 在每个请求上加载,如果需要,它将返回一个新的 cookie。 |
多页应用程序
对于在每个页面上加载 JavaScript 的多页面应用程序,在 cookie 中公开 CSRF Tokens的替代方法是将 CSRF Tokens包含在meta
标签。
HTML 可能如下所示:
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
</html>
为了在请求中包含 CSRF Tokens,您可以利用以下事实: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>
一旦元标记包含 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 应用程序的另一种选择是在 HTTP 响应标头中包含 CSRF Tokens。
实现这一目标的一种方法是使用@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 Tokens现在将在响应标头 (X-CSRF-TOKEN
或X-XSRF-TOKEN
默认情况下),控制器通知适用于任何自定义端点。
对后端的任何请求都可用于从响应中获取Tokens,后续请求可以将Tokens包含在具有相同名称的请求标头中。
移动应用程序
与 JavaScript 应用程序一样,移动应用程序通常使用 JSON 而不是 HTML。 不为浏览器流量提供服务的后端应用程序可以选择禁用 CSRF。 在这种情况下,不需要额外的工作。
但是,也为浏览器流量提供服务并因此仍需要 CSRF 保护的后端应用程序可能会继续存储CsrfToken
在会话中而不是在 cookie 中。
在这种情况下,与后端集成的典型模式是公开/csrf
endpoint 以允许前端(移动或浏览器客户端)按需请求 CSRF Tokens。
使用此模式的好处是 CSRF Tokens可以继续延迟,并且仅在请求需要 CSRF 保护时才需要从会话加载。
使用自定义终结点还意味着客户端应用程序可以通过发出显式请求来请求按需生成新Tokens(如有必要)。
此模式可用于需要 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 Tokens。
需要在身份验证成功和注销成功后刷新Tokens,因为 |
获得 CSRF Tokens后,您需要将其作为 HTTP 请求标头(其中一个X-CSRF-TOKEN
或X-XSRF-TOKEN
默认情况下)自己。
处理AccessDeniedException
要处理AccessDeniedException
如InvalidCsrfTokenException
,您可以配置 Spring Security 以您喜欢的任何方式处理这些异常。例如,您可以使用以下配置配置自定义拒绝访问页面:
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
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"));
}
}
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
fun loginWhenInvalidCsrfTokenThenForbidden() {
mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@Test
fun loginWhenMissingCsrfTokenThenForbidden() {
mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@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 Tokens,并且恶意用户无法强制注销您的用户。
最简单的方法是使用表单将用户注销。如果您真的想要一个链接,您可以使用 JavaScript 让链接执行 POST(可能在隐藏表单上)。对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到执行 POST 的注销确认页面。
如果您真的想在注销时使用 HTTP GET,您可以这样做。但是,请记住,通常不建议这样做。例如,以下内容在/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()
}
}
有关更多信息,请参阅注销一章。
CSRF 和会话超时
默认情况下,Spring Security 将 CSRF Tokens存储在HttpSession
使用HttpSessionCsrfTokenRepository
. 这可能会导致会话过期,没有 CSRF Tokens进行验证。
我们已经讨论了会话超时的一般解决方案。本节讨论与 servlet 支持相关的 CSRF 超时的细节。
您可以将 CSRF Tokens的存储更改为 cookie 中。有关详细信息,请参阅使用CookieCsrfTokenRepository
部分。
如果Tokens确实过期,您可能希望通过指定习惯AccessDeniedHandler
. 自定义AccessDeniedHandler
可以处理InvalidCsrfTokenException
任何你喜欢的方式。
分段(文件上传)
我们已经讨论过保护多部分请求(文件上传)免受 CSRF 攻击如何导致先有鸡还是先有蛋的问题。当 JavaScript 可用时,我们建议在 HTTP 请求标头中包含 CSRF Tokens以规避问题。
您可以在 Spring 参考的 Multipart Resolver 部分和 |
将 CSRF Tokens放入正文中
我们已经讨论了将 CSRF Tokens放在正文中的权衡。在本节中,我们将讨论如何配置 Spring Security 以从正文中读取 CSRF。
要从正文中读取 CSRF Tokens,请使用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 Tokens
如果允许未经授权的用户上传临时文件是不可接受的,另一种方法是将MultipartFilter
在 Spring Security 过滤器之后,并将 CSRF 作为查询参数包含在表单的 action 属性中。由于CsrfToken
被公开为HttpServletRequest
名为_csrf
,我们可以使用它来创建一个action
其中有 CSRF Tokens。以下示例使用 JSP 执行此作:
<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">
HiddenHttp方法过滤器
我们已经讨论了将 CSRF Tokens放入正文中的权衡。
在 Spring 的 Servlet 支持中,覆盖 HTTP 方法是使用HiddenHttpMethodFilter
. 您可以在参考文档的 HTTP 方法转换部分找到更多信息。