|
对于最新的稳定版本,请使用 Spring Security 6.5.3! |
Servlet 环境的跨站点请求伪造 (CSRF)
本节讨论 Spring Security 对 servlet 环境的跨站点请求伪造 (CSRF) 支持。
使用 Spring Security CSRF 保护
下面概述了使用 Spring Security 的 CSRF 保护的步骤:
使用正确的 HTTP 动词
防范 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 动词。 安全方法必须是只读中详细介绍了这一点。
配置 CSRF 保护
下一步是在应用程序中配置 Spring Security 的 CSRF 保护。 默认情况下,Spring Security 的 CSRF 保护处于启用状态,但您可能需要自定义配置。 以下是一些常见的自定义。
自定义 CsrfTokenRepository
默认情况下,Spring Security 将预期的 CSRF Tokens存储在HttpSession用HttpSessionCsrfTokenRepository.
在某些情况下,用户可能希望配置自定义CsrfTokenRepository.
例如,可能需要保留CsrfToken在 cookie 中支持基于 JavaScript 的应用程序。
默认情况下,CookieCsrfTokenRepository将写入名为XSRF-TOKEN并从名为X-XSRF-TOKEN或 HTTP 参数_csrf.
这些默认值来自 AngularJS
您可以配置CookieCsrfTokenRepository在 XML 中使用以下命令:
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
|
示例显式设置 |
您可以配置CookieCsrfTokenRepository在 Java 配置中使用:
-
Java
-
Kotlin
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf {
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
}
}
return http.build()
}
}
|
示例显式设置 |
禁用 CSRF 保护
默认情况下,CSRF 保护处于启用状态。 但是,如果对您的应用程序有意义,则禁用 CSRF 保护很简单。
下面的 XML 配置将禁用 CSRF 保护。
<http>
<!-- ... -->
<csrf disabled="true"/>
</http>
下面的 Java 配置将禁用 CSRF 保护。
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable());
return http.build();
}
}
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf {
disable()
}
}
return http.build()
}
}
配置 CsrfTokenRequestHandler
Spring Security 的 CsrfFilter 将 CsrfToken 公开为HttpServletRequest名为_csrf在 CsrfTokenRequestHandler 的帮助下。
默认实现是CsrfTokenRequestAttributeHandler.
替代实现XorCsrfTokenRequestAttributeHandler可用于为 BREACH 提供保护(参见 GH-4001)。
您可以配置XorCsrfTokenRequestAttributeHandler在 XML 中使用以下命令:
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>
您可以配置XorCsrfTokenRequestAttributeHandler在 Java 配置中使用以下命令:
-
Java
-
Kotlin
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
);
return http.build();
}
}
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf {
csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
}
}
return http.build()
}
}
包括 CSRF Tokens
为了使同步器Tokens模式能够防止 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF Tokens。 这必须包含在浏览器不会自动包含在 HTTP 请求中的请求的一部分(即表单参数、HTTP 标头等)中。
我们已经看到CsrfToken作为请求属性公开。
这意味着任何视图技术都可以访问CsrfToken将预期的Tokens公开为表单或元标记。
幸运的是,下面列出了一些集成,使在表单和 ajax 请求中包含Tokens变得更加容易。
表单 URL 编码
为了发布 HTML 表单,必须将 CSRF Tokens作为隐藏输入包含在表单中。 例如,呈现的 HTML 可能如下所示:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
接下来,我们将讨论将 CSRF Tokens包含在表单中作为隐藏输入的各种方法。
自动包含 CSRF Tokens
Spring Security 的 CSRF 支持通过其 CsrfRequestDataValueProcessor 提供与 Spring 的 RequestDataValueProcessor 的集成。
这意味着,如果您利用 Spring 的表单标签库、Thymeleaf 或任何其他与RequestDataValueProcessor,则具有不安全 HTTP 方法(即 post)的表单将自动包含实际的 CSRF Tokens。
csrfInput 标签
如果您使用的是 JSP,那么您可以使用 Spring 的表单标签库。 但是,如果这不是一个选项,您也可以轻松地将Tokens包含在 csrfInput 标记中。
Ajax 和 JSON 请求
如果您使用的是 JSON,则无法在 HTTP 参数中提交 CSRF Tokens。 相反,您可以在 HTTP 标头中提交Tokens。
在以下部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF Tokens作为 HTTP 请求标头包含的各种方法。
元标记
在 cookie 中公开 CSRF 的另一种模式是在meta标签。
HTML 可能如下所示:
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
一旦元标记包含 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);
});
});
csrfMeta 标记
如果您使用的是 JSP,则将 CSRF Tokens写入meta标签是通过利用 csrfMeta 标签。
CsrfToken 请求属性
如果在请求中包含实际 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>
<!-- ... -->
CSRF 注意事项
在实施针对 CSRF 攻击的保护时,需要考虑一些特殊注意事项。 本节讨论与 servlet 环境相关的这些注意事项。 请参阅 CSRF 注意事项 以进行更一般的讨论。
登录
请务必对登录请求要求 CSRF,以防止伪造登录尝试。 Spring Security 的 servlet 支持开箱即用地执行此作。
注销
请务必要求注销请求使用 CSRF 以防止伪造注销尝试。如果启用了 CSRF 保护(默认),则 Spring Security 的LogoutFilter仅处理 HTTP POST。这可确保注销需要 CSRF Tokens,并且恶意用户无法强制注销您的用户。
最简单的方法是使用表单注销。如果您真的想要一个链接,您可以使用 JavaScript 让链接执行 POST(即可能在隐藏表单上)。对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到将执行 POST 的注销确认页面。
如果您真的想在注销时使用 HTTP GET,您可以这样做,但请记住,通常不建议这样做。例如,以下 Java 配置将使用 URL/logout使用任何 HTTP 方法请求:
-
Java
-
Kotlin
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
);
return http.build();
}
}
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
logout {
logoutRequestMatcher = AntPathRequestMatcher("/logout")
}
}
return http.build()
}
}
CSRF 和会话超时
默认情况下,Spring Security 将 CSRF Tokens存储在HttpSession. 这可能会导致会话过期的情况,这意味着没有预期的 CSRF Tokens可以验证。
我们已经讨论了会话超时的一般解决方案。本节讨论与 servlet 支持相关的 CSRF 超时的细节。
将预期的 CSRF Tokens的存储更改为 Cookie 中的存储非常简单。有关详细信息,请参阅自定义 CsrfTokenRepository 部分。
分段(文件上传)
|
有关在 Spring 中使用多部分表单的更多信息,可以在 1.1.11 中找到。Spring 参考的 Multipart Resolver 部分和 MultipartFilter javadoc。 |
将 CSRF Tokens放入正文中
我们已经讨论了将 CSRF Tokens放在正文中的权衡。在本节中,我们将讨论如何配置 Spring Security 以从正文中读取 CSRF。
为了从正文中读取 CSRF Tokens,MultipartFilter在 Spring Security 过滤器之前指定。指定MultipartFilter之前 Spring Security 过滤器意味着没有调用MultipartFilter这意味着任何人都可以将临时文件放置在您的服务器上。但是,只有授权用户才能提交由您的应用程序处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响应该可以忽略不计。
确保MultipartFilter在使用 java 配置的 Spring Security 过滤器之前指定,用户可以覆盖 beforeSpringSecurityFilterChain,如下所示:
-
Java
-
Kotlin
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())
}
}
确保MultipartFilter在使用 XML 配置的 Spring Security 过滤器之前指定,用户可以><确保MultipartFilter放置在web.xml中的springSecurityFilterChain之前,如下所示:
<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 request 属性,我们可以使用它来创建一个action其中包含 CSRF Tokens。
下面显示了 jsp 的示例
<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">
HiddenHttp方法过滤器
我们已经讨论了将 CSRF Tokens放入正文中的权衡。
在 Spring 的 Servlet 支持中,使用 HiddenHttpMethodFilter 覆盖 HTTP 方法。 更多信息可以在参考文档的 HTTP 方法转换部分找到。