CSRF攻击可能的原因是来自受害者网站的HTTP请求和来自攻击者网站的请求完全相同。这意味着无法拒绝来自恶意网站的请求,而允许来自银行网站的请求。为了防止CSRF攻击,我们需要确保在请求中有一些恶意网站无法提供的东西,这样我们就可以区分这两个请求。
Spring Security提供了两种机制来防止CSRF攻击:
-
同步器令牌模式(Synchronizer Token Pattern)
-
在会话cookie上指定SameSite属性
同步器令牌模式(Synchronizer Token Pattern)
防止CSRF攻击的主要和最全面的方法是使用同步器令牌模式。这个解决方案是为了确保每个HTTP请求,除了我们的会话cookie之外,必须在HTTP请求中提供一个名为CSRF令牌的安全随机生成的值。
当提交一个HTTP请求时,服务器必须查找预期的CSRF令牌,并将其与HTTP请求中的实际CSRF令牌进行比较。如果值不匹配,HTTP请求应该被拒绝。
此工作的关键是实际的CSRF令牌应该在HTTP请求的一部分中,而浏览器不自动包含该部分。例如,在HTTP参数或HTTP报头中要求实际的CSRF令牌将防止CSRF攻击。在cookie中要求实际的CSRF令牌不起作用,因为浏览器会自动将cookie包含在HTTP请求中。
我们可以放宽期望,对更新应用程序状态的每个HTTP请求只需要实际的CSRF令牌。为了实现这一点,应用程序必须确保安全的HTTP方法是幂等的。这提高了可用性,因为我们希望允许从外部网站链接到我们的网站。此外,尽量不要在HTTPGET
中包含随机令牌,因为这会导致令牌泄露。
假设实际的CSRF令牌需要位于一个名为_csrf的HTTP参数中。申请转帐表达将修改为如下:
<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属性的值实际是由服务端填到页面上的,
使用thymeleaf示例如下
<input type="hidden"
name="_csrf"
th:value="${_csrf.getToken()}"/>
表单现在包含一个包含CSRF令牌值的隐藏输入。外部站点无法读取CSRF令牌,因为同源策略确保恶意站点无法读取响应。
相应的转账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&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
注意到HTTP请求现在包含了_csrf参数和一个安全的随机值。恶意网站将不能提供正确的csrf
参数值,服务器判定为非法访问,拒绝恶意网站的请求