对于最新的稳定版本,请使用 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

这两种保护都要求 Safe Methods 为 Idempotentspring-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 模式时,我们的示例将如何变化。 假设实际的 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

  • Strict:指定后,来自同一站点的任何请求都包含 Cookie。 否则,该 Cookie 将不包含在 HTTP 请求中。spring-doc.cn

  • Lax:指定后,当 Cookie 来自同一站点或请求来自顶级导航且方法为幂等时,将发送 Cookie。 否则,该 Cookie 将不包含在 HTTP 请求中。spring-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的 Spring MVC 应用程序仍然可以通过将 URL 后缀更新为以 结尾 来利用,如下所示:Content-Type.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,该 cookie 包含其中包含所有状态进行身份验证(而不是 JSESSIONID)。 当进行 CSRF 攻击时,自定义 cookie 与请求一起发送的方式与上一个示例中发送 JSESSIONID cookie 的方式相同。 此应用程序容易受到 CSRF 攻击。spring-doc.cn

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

CSRF 注意事项

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

登录

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

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

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

  3. 该信息与恶意用户的帐户相关联,因此恶意用户可以使用自己的凭据登录并查看受害者的敏感信息。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

对 multipart/form-data 使用 CSRF 保护有两种选择:spring-doc.cn

每个选项都有其权衡取舍。spring-doc.cn

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

将 CSRF 令牌放在正文中

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

在 URL 中包含 CSRF 令牌

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

隐藏的HttpMethodFilter

某些应用程序可以使用 form 参数来覆盖 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