# Servlet 环境中的跨站点请求伪造
本节讨论 Spring Security对 Servlet 环境的跨站点请求伪造支持。
# 使用 Spring 安全CSRF保护
使用 Spring Security的CSRF保护的步骤概述如下:
# 使用适当的HTTP动词
防止CSRF攻击的第一步是确保你的网站使用正确的HTTP动词。这在安全的方法必须是幂等的。中有详细介绍。
# 配置CSRF保护
下一步是在应用程序中配置 Spring Security的CSRF保护。 Spring 默认情况下,Security的CSRF保护是启用的,但你可能需要定制配置。下面是一些常见的定制。
# 自定义CSRFTokenRepository
Spring 默认情况下,Security使用HttpSessionCsrfTokenRepository
将预期的CSRF令牌存储在HttpSession
中。在某些情况下,用户可能希望配置自定义CsrfTokenRepository
。例如,可能希望将cookie中的CsrfToken
持久化到支持基于JavaScript的应用程序。
默认情况下,CookieCsrfTokenRepository
将写到一个名为XSRF-TOKEN
的cookie,并从一个名为X-XSRF-TOKEN
的头部或HTTP参数_csrf
读取它。这些默认值来自AngularJS (opens new window)
可以使用以下方法在XML中配置CookieCsrfTokenRepository
:
例1.使用XML配置在cookie中存储CSRF令牌
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
示例显式设置cookieHttpOnly=false .这是允许JavaScript(即Angularjs)读取它所必需的。 如果不需要直接使用JavaScript读取cookie的能力,建议省略 cookieHttpOnly=false 以提高安全性。 |
---|
你可以在 Java 配置中使用以下方法配置CookieCsrfTokenRepository
:
例2.在cookie中存储CSRF令牌
Java
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
}
}
Kotlin
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
}
}
}
}
示例显式设置cookieHttpOnly=false .这是允许JavaScript(即Angularjs)读取它所必需的。 如果不需要直接使用JavaScript读取cookie的能力,建议省略 cookieHttpOnly=false (通过使用new CookieCsrfTokenRepository() 代替)以提高安全性。 |
---|
# 禁用CSRF保护
默认情况下启用了CSRF保护。但是,如果CSRF保护对你的应用程序来说是有意义的,则禁用CSRF保护非常简单。
下面的XML配置将禁用CSRF保护。
例3.禁用CSRF XML配置
<http>
<!-- ... -->
<csrf disabled="true"/>
</http>
下面的 Java 配置将禁用CSRF保护。
例4.禁用CSRF
Java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
.csrf(csrf -> csrf.disable());
}
}
Kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
disable()
}
}
}
}
# 包括CSRF令牌
为了使同步器令牌模式能够抵御CSRF攻击,我们必须在HTTP请求中包含实际的CSRF令牌。这必须包含在请求的一部分(即表单参数、HTTP头等)中,而该部分不是由浏览器自动包含在HTTP请求中的。
Spring Security的CsrfFilter (opens new window)将CsrfToken (opens new window)公开为名为HttpServletRequest
的属性。这意味着,任何视图技术都可以访问CsrfToken
以将预期的令牌公开为form或meta tag。幸运的是,下面列出的集成使得在form和ajax请求中包含令牌变得更加容易。
# 表单URL编码
为了发布HTML表单,CSRF令牌必须作为隐藏输入包含在表单中。例如,呈现的HTML可能看起来像:
例5. CSRF令牌HTML
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
接下来,我们将讨论将CSRF令牌以一种形式包含为隐藏输入的各种方法。
# CSRF令牌自动包含
Spring Security的CSRF支持通过其CsrfrequestDataValueProcessor (opens new window)提供与 Spring 的RequestDataValueProcessor (opens new window)的集成。这意味着,如果你利用Spring’s form tag library (opens new window)、Thymeleaf (opens new window)或与RequestDataValueProcessor
集成的任何其他视图技术,那么具有不安全的HTTP方法(即POST)的窗体将自动包括实际的CSRF令牌。
# csrfinput标记
如果你正在使用JSP,那么你可以使用Spring’s form tag library (opens new window)。但是,如果这不是一个选项,你也可以很容易地将令牌包含在csrfInput标记中。
# CSRFToken请求属性
如果用于在请求中包含实际CSRF令牌的其他选择不起作用,则可以利用以下事实:CsrfToken
is exposed作为HttpServletRequest
属性,该属性名为_csrf
。
使用JSP执行此操作的示例如下所示:
例6.具有请求属性的表单中的CSRF令牌
<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>
# Ajax和JSON请求
如果你正在使用JSON,那么就不可能在HTTP参数中提交CSRF令牌。相反,你可以在HTTP头中提交令牌。
在下面的部分中,我们将讨论在基于JavaScript的应用程序中将CSRF令牌作为HTTP请求头包含在内的各种方法。
# 自动包含
Spring 安全性可以很容易地configured将预期的CSRF令牌存储在cookie中。通过将预期的CSRF存储在Cookie中,像AngularJS (opens new window)这样的JavaScript框架将自动在HTTP请求头中包含实际的CSRF令牌。
# 元标签
在cookie中暴露CSRF的另一种模式是在meta
标记中包含CSRF标记。HTML可能看起来是这样的:
例7. CSRF元标记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,可以通过以下方式完成此操作:
例8. Ajax发送CSRF令牌
$(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令牌写入meta
标记的一种简单方法是利用csrfMeta标记。
# CSRFToken请求属性
如果用于在请求中包含实际CSRF令牌的其他选择不起作用,则可以利用以下事实:CsrfToken
is exposed作为HttpServletRequest
属性,该属性名为_csrf
。使用JSP执行此操作的示例如下所示:
例9. 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令牌,并且恶意用户不能强制注销你的用户。
最简单的方法是使用表单注销。如果你真的想要一个链接,可以使用JavaScript让该链接执行一个POST(例如,可能在一个隐藏的表单上)。对于禁用了JavaScript的浏览器,你可以选择让链接将用户带到将执行POST的注销确认页面。
如果你真的想使用HTTP GET与注销,你可以这样做,但请记住,这通常是不推荐的。例如,以下 Java 配置将使用任何HTTP方法请求的URL执行注销:
例10.用HTTP GET登出
Java
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
);
}
}
Kotlin
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutRequestMatcher = AntPathRequestMatcher("/logout")
}
}
}
}
# CSRF和会话暂停
默认情况下, Spring Security将CSRF令牌存储在HttpSession
中。这可能导致会话过期的情况,这意味着没有预期的CSRF令牌来验证。
我们已经讨论了一般解决方案到会话的超时。本节讨论CSRF超时的细节,因为它与 Servlet 支持有关。
将预期的CSRF令牌的存储更改为cookie中的存储是很简单的。有关详细信息,请参阅自定义CSRFTokenRepository部分。
如果一个令牌确实过期了,你可能希望通过指定一个自定义AccessDeniedHandler
来定制它的处理方式。自定义AccessDeniedHandler
可以以任何方式处理InvalidCsrfTokenException
。对于如何自定义AccessDeniedHandler
的示例,请参阅为xml和Java configuration (opens new window)提供的链接。
#
我们有已经讨论过了如何保护多部分请求(文件上传)不受CSRF攻击导致鸡和蛋 (opens new window)问题。本节讨论如何在 Servlet 应用程序中实现将CSRF令牌放置在body和url中。
关于使用具有 Spring 的多部分表单的更多信息,可以在 Spring 引用的1.1.11. 多部分旋转变压器 (opens new window)部分和MultipartFilter Javadoc (opens new window)中找到。 |
---|
# 将CSRF标记放入体内
我们有已经讨论过了在主体中放置CSRF标记的权衡。在本节中,我们将讨论如何配置 Spring 安全性,以便从主体读取CSRF。
为了从主体中读取CSRF令牌,在 Spring 安全过滤器之前指定了MultipartFilter
。在 Spring 安全过滤器之前指定MultipartFilter
意味着没有调用MultipartFilter
的授权,这意味着任何人都可以在你的服务器上放置临时文件。但是,只有经过授权的用户才能提交由你的应用程序处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。
为了确保MultipartFilter
是在 Spring 安全过滤器 Java 配置之前指定的,用户可以在SpringSecurityFilterchain之前覆盖,如下所示:
例11.初始化器MultipartFilter
Java
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
Kotlin
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
insertFilters(servletContext, MultipartFilter())
}
}
为了确保在使用XML配置的 Spring 安全过滤器之前指定MultipartFilter
,用户可以确保将MultipartFilter
元素的<filter-mapping>元素放置在web.xml中的SpringSecurityFilterchain之前,如下所示:
示例12.web.xml-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 安全过滤器之后,并将CSRF作为查询参数包含在表单的动作属性中。由于CsrfToken
被公开为HttpServletRequest
请求属性,因此我们可以使用它来创建带有CSRF令牌的action
。下面显示了一个带有JSP的示例。
例13. CSRF令牌正在运行
<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">
# HiddenHttpMethodFilter
我们有已经讨论过了在主体中放置CSRF标记的权衡。
在 Spring 的 Servlet 支持中,覆盖HTTP方法是使用HiddenHttpMethodFilter (opens new window)完成的。更多信息可以在参考文档的HTTP方法转换 (opens new window)部分中找到。
← 保护免受剥削 安全HTTP响应标头 →