对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cn

跨站点请求伪造 (CSRF)

Spring 为防止跨站点请求伪造 (CSRF) 攻击提供了全面的支持。 在以下部分中,我们将探讨:spring-doc.cn

文档的这一部分讨论了 CSRF 保护的一般主题。 有关基于 servletWebFlux 的应用程序的 CSRF 保护的具体信息,请参阅相关部分。spring-doc.cn

什么是 CSRF 攻击?

理解 CSRF 攻击的最好方法是看一个具体的例子。spring-doc.cn

假设您的银行网站提供了一个表单,允许将资金从当前登录的用户转移到另一个银行账户。 例如,转移表单可能如下所示:spring-doc.cn

转移表格
<form method="post"
	action="/transfer">
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="text"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

相应的 HTTP 请求可能如下所示:spring-doc.cn

传输 HTTP 请求
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

现在假装您验证了银行的网站,然后在不注销的情况下访问了一个邪恶的网站。 邪恶的网站包含一个 HTML 页面,其格式如下:spring-doc.cn

邪恶转移表
<form method="post"
	action="https://bank.example.com/transfer">
<input type="hidden"
	name="amount"
	value="100.00"/>
<input type="hidden"
	name="routingNumber"
	value="evilsRoutingNumber"/>
<input type="hidden"
	name="account"
	value="evilsAccountNumber"/>
<input type="submit"
	value="Win Money!"/>
</form>

您想赢钱,所以您点击提交按钮。 在此过程中,您无意中将 100 USD 转给了恶意用户。 发生这种情况是因为,虽然邪恶网站无法看到您的 cookie,但与您的银行关联的 cookie 仍会与请求一起发送。spring-doc.cn

最糟糕的是,整个过程本来可以使用 JavaScript 自动化。 这意味着您甚至不需要单击按钮。 此外,当访问成为 XSS 攻击受害者的诚实网站时,它也很容易发生。 那么,我们如何保护我们的用户免受此类攻击呢?spring-doc.cn

防范 CSRF 攻击

CSRF 攻击之所以可能发生,是因为受害者网站的 HTTP 请求和攻击者网站的请求完全相同。 这意味着无法拒绝来自恶意网站的请求并允许来自银行网站的请求。 为了防止 CSRF 攻击,我们需要确保请求中有恶意网站无法提供的内容,以便我们可以区分这两个请求。spring-doc.cn

Spring 提供了两种机制来防止 CSRF 攻击:spring-doc.cn

安全方法必须是幂等的

为了使针对 CSRF 的任一保护工作,应用程序必须确保“安全”的 HTTP 方法是幂等的。 这意味着使用 HTTP 方法 、 、 和 的请求不应更改应用程序的状态。GETHEADOPTIONSTRACEspring-doc.cn

同步器令牌模式

防止 CSRF 攻击的主要和最全面的方法是使用 Synchronizer Token Pattern。 该解决方案是为了确保每个 HTTP 请求除了我们的会话 cookie 之外,HTTP 请求中还必须存在一个称为 CSRF 令牌的安全随机生成值。spring-doc.cn

提交 HTTP 请求时,服务器必须查找预期的 CSRF 令牌,并将其与 HTTP 请求中的实际 CSRF 令牌进行比较。 如果值不匹配,则应拒绝 HTTP 请求。spring-doc.cn

这项工作的关键是实际的 CSRF 令牌应该位于浏览器不会自动包含的 HTTP 请求的一部分中。 例如,在 HTTP 参数或 HTTP 标头中要求实际的 CSRF 令牌将防止 CSRF 攻击。 在 cookie 中要求实际的 CSRF 令牌不起作用,因为浏览器会自动将 cookie 包含在 HTTP 请求中。spring-doc.cn

我们可以放宽期望,只要求每个更新应用程序状态的 HTTP 请求都使用实际的 CSRF 令牌。 为此,我们的应用程序必须确保安全的 HTTP 方法是幂等的。 这提高了可用性,因为我们希望允许使用来自外部网站的链接链接到我们的网站。 此外,我们不希望在 HTTP GET 中包含随机令牌,因为这可能会导致令牌泄露。spring-doc.cn

让我们看看在使用 Synchronizer Token Pattern 时,我们的示例将如何变化。 假设实际的 CSRF 令牌需要位于名为 的 HTTP 参数中。 我们申请的转移表格将如下所示:_csrfspring-doc.cn

同步器令牌形式
<form method="post"
	action="/transfer">
<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="hidden"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

表单现在包含一个隐藏的输入,其值为 CSRF 令牌。 外部站点无法读取 CSRF 令牌,因为同源策略确保恶意站点无法读取响应。spring-doc.cn

相应的转账 HTTP 请求如下所示:spring-doc.cn

Synchronizer Token 请求
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

您将注意到,HTTP 请求现在包含具有安全随机值的参数。 邪恶网站将无法为参数提供正确的值(必须在邪恶网站上明确提供),并且当服务器将实际的 CSRF 令牌与预期的 CSRF 令牌进行比较时,传输将失败。_csrf_csrfspring-doc.cn

SameSite 属性

防止 CSRF 攻击的一种新兴方法是在 Cookie 上指定 SameSite 属性。 服务器可以在设置 Cookie 时指定该属性,以指示在来自外部站点时不应发送 Cookie。SameSitespring-doc.cn

Spring Security 不直接控制会话 cookie 的创建,因此它不提供对 SameSite 属性的支持。Spring Session 在基于 servlet 的应用程序中为该属性提供支持。 Spring 框架的CookieWebSessionIdResolver为基于 WebFlux 的应用程序中的属性提供了开箱即用的支持。SameSiteSameSitespring-doc.cn

例如,具有该属性的 HTTP 响应标头可能如下所示:SameSitespring-doc.cn

SameSite HTTP 响应
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

该属性的有效值为:SameSitespring-doc.cn

让我们看看如何使用 attribute 保护我们的示例。 银行应用程序可以通过在会话 cookie 上指定属性来防止 CSRF。SameSiteSameSitespring-doc.cn

在我们的会话 cookie 上设置属性后,浏览器将继续发送来自银行网站的请求 cookie。 但是,浏览器将不再发送带有来自恶意网站的传输请求的 cookie。 由于来自恶意网站的传输请求中不再存在该会话,因此该应用程序受到保护免受 CSRF 攻击。SameSiteJSESSIONIDJSESSIONIDspring-doc.cn

在使用 attribute 来防止 CSRF 攻击时,应该注意一些重要的注意事项SameSitespring-doc.cn

将属性设置为 可提供更强的防御,但可能会使用户感到困惑。 考虑一个用户一直登录到托管在 social.example.com 的社交媒体网站。 用户在 email.example.org 会收到一封电子邮件,其中包含指向社交媒体网站的链接。 如果用户点击该链接,他们理所当然地希望通过社交媒体网站的身份验证。 但是,如果属性为 ,则不会发送 Cookie,因此不会对用户进行身份验证。SameSiteStrictSameSiteStrictspring-doc.cn

我们可以通过实施 gh-7537 来提高对 CSRF 攻击的保护作用和可用性。SameSitespring-doc.cn

另一个明显的考虑因素是,为了使该属性能够保护用户,浏览器必须支持该属性。 大多数现代浏览器都支持 SameSite 属性。 但是,仍在使用的旧浏览器可能无法使用。SameSiteSameSitespring-doc.cn

因此,通常建议使用该属性作为深度防御,而不是唯一防御 CSRF 攻击。SameSitespring-doc.cn

何时使用 CSRF 保护

什么时候应该使用 CSRF 保护? 我们的建议是对普通用户可以由浏览器处理的任何请求使用 CSRF 保护。 如果您只创建非浏览器客户端使用的服务,则可能需要禁用 CSRF 保护。spring-doc.cn

CSRF 保护和 JSON

一个常见的问题是“我需要保护 javascript 发出的 JSON 请求吗? 简短的回答是,这要看情况。 但是,您必须非常小心,因为存在可能影响 JSON 请求的 CSRF 漏洞。 例如,恶意用户可以使用以下形式使用 JSON 创建 CSRFspring-doc.cn

使用 JSON 表单的 CSRF
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
	<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
	<input type="submit"
		value="Win Money!"/>
</form>

这将生成以下 JSON 结构spring-doc.cn

带有 JSON 请求的 CSRF
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

如果应用程序没有验证 Content-Type,那么它将受到此漏洞的影响。 根据设置,验证Content-Type的 Spring MVC 应用程序仍然可以通过更新 URL 后缀来利用,如下所示:.jsonspring-doc.cn

CSRF 与 JSON Spring MVC 表单
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
	<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
	<input type="submit"
		value="Win Money!"/>
</form>

CSRF 和无状态浏览器应用程序

如果我的应用程序是无状态的,该怎么办? 这并不一定意味着您受到保护。 事实上,如果用户不需要在 Web 浏览器中为给定请求执行任何操作,他们可能仍然容易受到 CSRF 攻击。spring-doc.cn

例如,假设一个应用程序使用包含其中所有状态的自定义 cookie 而不是 JSESSIONID 进行身份验证。 当进行 CSRF 攻击时,自定义 cookie 将随请求一起发送,其方式与上一个示例中发送 JSESSIONID cookie 的方式相同。 此应用程序将容易受到 CSRF 攻击。spring-doc.cn

使用基本身份验证的应用程序也容易受到 CSRF 攻击。 该应用程序容易受到攻击,因为浏览器将以与上一个示例中发送 JSESSIONID Cookie 相同的方式在任何请求中自动包含用户名和密码。spring-doc.cn

CSRF 注意事项

在实施针对 CSRF 攻击的保护时,需要考虑一些特殊的注意事项。spring-doc.cn

登录

为了防止伪造 log in 请求,应保护 log in HTTP 请求免受 CSRF 攻击。 防止伪造登录请求是必要的,这样恶意用户就无法读取受害者的敏感信息。 攻击执行如下:spring-doc.cn

  • 恶意用户使用恶意用户的凭证执行 CSRF 登录。 受害者现在被认证为恶意用户。spring-doc.cn

  • 然后,恶意用户诱骗受害者访问受感染的网站并输入敏感信息spring-doc.cn

  • 该信息与恶意用户的帐户相关联,因此恶意用户可以使用自己的凭据登录并查看 vicitim 的敏感信息spring-doc.cn

确保登录 HTTP 请求免受 CSRF 攻击的一个可能复杂之处在于,用户可能会遇到会话超时,从而导致请求被拒绝。 对于不希望需要会话才能登录的用户来说,会话超时会让人感到惊讶。 有关更多信息,请参阅 CSRF 和 Session Timeoutsspring-doc.cn

注销

为了防止伪造注销请求,应保护注销 HTTP 请求免受 CSRF 攻击。 防止伪造注销请求是必要的,这样恶意用户就无法读取受害者的敏感信息。 有关攻击的详细信息,请参阅此博客文章spring-doc.cn

确保注销 HTTP 请求免受 CSRF 攻击的一个可能复杂情况是,用户可能会遇到会话超时,从而导致请求被拒绝。 对于不希望需要会话才能注销的用户来说,会话超时会让人感到惊讶。 有关更多信息,请参阅 CSRF 和 Session Timeoutsspring-doc.cn

CSRF 和会话超时

通常,预期的 CSRF 令牌存储在 session 中。 这意味着一旦会话过期,服务器将找不到预期的 CSRF 令牌并拒绝 HTTP 请求。 有许多选项可以解决超时问题,每个选项都需要权衡取舍。spring-doc.cn

  • 缓解超时的最佳方法是在提交表单时使用 JavaScript 请求 CSRF 令牌。 然后使用 CSRF 令牌更新表单并提交。spring-doc.cn

  • 另一种选择是有一些 JavaScript,让用户知道他们的会话即将过期。 用户可以单击按钮以继续并刷新会话。spring-doc.cn

  • 最后,预期的 CSRF 令牌可以存储在 cookie 中。 这允许预期的 CSRF 令牌比 session 存活。spring-doc.cn

    有人可能会问为什么默认情况下预期的 CSRF 令牌不存储在 cookie 中。 这是因为存在已知的漏洞,其中标头(例如,用于指定 Cookie)可由另一个域设置。 这与 Ruby on Rails 在头文件 X-Requested-With 存在时不再跳过 CSRF 检查的原因相同。 有关如何执行漏洞利用的详细信息,请参阅此 webappsec.org 线程。 另一个缺点是,通过删除状态(即超时),您将失去在令牌泄露时强制使令牌失效的能力。spring-doc.cn

分段(文件上传)

保护分段请求(文件上传)免受 CSRF 攻击会导致先有鸡还是先有蛋的问题。 为了防止 CSRF 攻击的发生,必须读取 HTTP 请求的正文以获取实际的 CSRF 令牌。 但是,读取正文意味着将上传文件,这意味着外部站点可以上传文件。spring-doc.cn

将 CSRF 保护与 multipart/form-data 一起使用有两种选择。 每个选项都有其权衡取舍。spring-doc.cn

在将 Spring Security 的 CSRF 保护与多部分文件上传集成之前,请确保您可以先在没有 CSRF 保护的情况下上传。 有关在 Spring 中使用多部分形式的更多信息,请参见 1.1.11 。Multipart Resolver 部分和 MultipartFilter javadocspring-doc.cn

将 CSRF 令牌放在正文中

第一个选项是在请求正文中包含实际的 CSRF 令牌。 通过将 CSRF 令牌放在 body 中,将在执行授权之前读取 body。 这意味着任何人都可以在您的服务器上放置临时文件。 但是,只有授权用户才能提交由您的应用程序处理的文件。 通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响应该可以忽略不计。spring-doc.cn

在 URL 中包含 CSRF 令牌

如果不允许未经授权的用户上传临时文件,另一种方法是将预期的 CSRF 令牌作为查询参数包含在表单的 action 属性中。 此方法的缺点是查询参数可能会泄露。 更一般地说,将敏感数据放在正文或标头中以确保其不会泄露被认为是最佳实践。 其他信息可以在 RFC 2616 第 15.1.3 节 在 URI 中编码敏感信息中找到。spring-doc.cn

隐藏的HttpMethodFilter

在某些应用程序中,表单参数可用于覆盖 HTTP 方法。 例如,下面的表单可用于将 HTTP 方法视为 a 而不是 .deletepostspring-doc.cn

CSRF 隐藏 HTTP 方法表单
<form action="/process"
	method="post">
	<!-- ... -->
	<input type="hidden"
		name="_method"
		value="delete"/>
</form>

覆盖 HTTP 方法发生在过滤器中。 该过滤器必须放在 Spring Security 的支持之前。 请注意,覆盖仅发生在 上,因此这实际上不太可能导致任何实际问题。 但是,最好还是确保将其放在 Spring Security 的过滤器之前。postspring-doc.cn