Spring Security 3.2.0.RC1 亮点:CSRF 防护

工程 | Rob Winch | 2013年8月21日 | ...

[callout title=更新]

这篇博客文章不再维护。请参考 CSRF 文档 以获取 Spring Security 和 CSRF 防护的最新信息。

[/callout]

周一我宣布了 Spring Security 3.2.0.RC1 的发布。这是介绍 Spring Security 3.2.0.RC1 新特性的两篇博客系列文章中的第一篇。

在这第一篇文章中,我将介绍 Spring Security 对 CSRF 的支持。在下一篇文章中,我将介绍已添加的各种安全头部。

CSRF 攻击

Spring Security 添加了针对跨站请求伪造 (CSRF) 攻击的防护。很好,但是 CSRF 攻击是什么?Spring Security 如何保护我免受其害?让我们看一个具体的例子来更好地理解。

假设您银行的网站提供一个表单,允许将资金从当前登录用户转移到另一个银行账户。例如,HTTP 请求可能看起来像

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

现在假设您登录了您银行的网站,然后没有退出,访问了一个恶意网站。这个恶意网站包含一个带有以下表单的 HTML 页面

<form action="https://bank.example.com/transfer" method="post">
  <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 美元转移给了恶意用户。发生这种情况是因为,虽然恶意网站无法看到您的 cookie,但与您的银行相关的 cookie 仍然随请求一起发送。

更糟糕的是,整个过程可以使用 JavaScript 自动化。这意味着您甚至不需要点击按钮。那么我们如何保护自己免受此类攻击呢?

同步器令牌模式

问题在于,来自银行网站的 HTTP 请求和来自恶意网站的请求是完全相同的。这意味着无法拒绝来自恶意网站的请求,同时也允许来自银行网站的请求。为了防范 CSRF 攻击,我们需要确保请求中存在一些恶意网站无法提供的内容。

一种解决方案是使用同步器令牌模式。该解决方案旨在确保每个请求除了会话 cookie 之外,还需要一个随机生成的令牌作为 HTTP 参数。提交请求时,服务器必须查找参数的预期值,并将其与请求中的实际值进行比较。如果值不匹配,请求应失败。

我们可以放宽要求,仅对更新状态的每个 HTTP 请求强制要求令牌。这可以安全地进行,因为同源策略确保恶意网站无法读取响应。此外,我们不希望将随机令牌包含在 HTTP GET 请求中,因为这可能导致令牌泄露

让我们看看我们的示例会如何变化。假设随机生成的令牌存在于一个名为 _csrf 的 HTTP 参数中。例如,转账请求将如下所示

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>

您会注意到我们添加了带有随机值的 _csrf 参数。现在,恶意网站将无法猜测 _csrf 参数的正确值(该值必须在恶意网站上明确提供),并且当服务器比较实际令牌与预期令牌时,转账将失败。

使用 Spring Security CSRF 支持

那么使用 Spring Security 保护我们的网站免受 CSRF 攻击需要哪些步骤呢?使用 Spring Security CSRF 防护的步骤如下所示

使用正确的 HTTP 动词

防范 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 动词。具体来说,在使用 Spring Security 的 CSRF 支持之前,您需要确定您的应用程序对任何修改状态的操作都使用了 PATCH、POST、PUT 和/或 DELETE 方法。这并非 Spring Security 支持的限制,而是正确进行 CSRF 防护的一般要求。

配置 CSRF 防护

下一步是在您的应用程序中包含 Spring Security 的 CSRF 防护。如果您使用 XML 配置,可以通过 <csrf /> 元素来完成

<http ...>
    ...
    <csrf />
</http>

使用 Java 配置时,CSRF 防护默认是启用的。如果您想禁用 CSRF,相应的 Java 配置如下所示

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
   WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      ...;
  }
}

包含 CSRF 令牌

表单提交

如果您正在使用 Spring MVC 的 form:form 标签,则会自动通过 CsrfRequestDataValueProcessor 为您包含 CsrfToken。

另外值得关注的是,一旦问题 7 得到解决,Thymeleaf 应该会实现自动集成。

最后一步是确保在所有 PATCH、POST、PUT 和 DELETE 方法中包含 CSRF 令牌。可以使用 _csrf 请求属性获取当前的 CsrfToken。以下是一个使用 JSP 完成此操作的示例


<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,则无法在 HTTP 参数中提交 CSRF 令牌。相反,您可以在 HTTP 头部中提交令牌。一个典型的模式是将 CSRF 令牌包含在您的 meta 标签中

<html>
  <head>
    <meta name="_csrf" content="${_csrf.token}"/>
    <!-- default header name is X-CSRF-TOKEN -->
    <meta name="_csrf_header" content="${_csrf.headerName}"/>
    ...
  </head>
  ...

然后您可以在所有 AJAX 请求中包含该令牌。如果您使用 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);
    });
});

CSRF 注意事项

实现 CSRF 时有一些注意事项。

超时

Cookies 中的 CSRF

有人可能会问为什么 CsrfToken 不存储在 cookie 中。这是因为存在已知漏洞,另一个域可以设置头部(即设置 cookie)。另一个缺点是,通过移除状态(即超时),如果您令牌被泄露,就失去了强制终止令牌的能力。

一个问题是预期的 CSRF 令牌存储在 HttpSession 中,因此一旦 HttpSession 过期,您配置的 AccessDeniedHandler 将收到一个 InvalidCsrfTokenException。如果您使用默认的 AccessDeniedHandler,浏览器将收到 HTTP 403 并显示一个糟糕的错误消息。

缓解活跃用户遭遇超时的一个简单方法是使用一些 JavaScript,让用户知道他们的会话即将过期。用户可以点击一个按钮来继续并刷新会话。

或者,指定自定义 AccessDeniedHandler 允许您以任何方式处理 InvalidCsrfTokenException。有关如何自定义 AccessDeniedHandler 的示例,请参考提供的 XMLJava 配置的链接。

登录

为了防止伪造登录请求,登录表单也应该受到 CSRF 攻击的保护。由于 CsrfToken 存储在 HttpSession 中,这意味着会立即创建一个 HttpSession。虽然这在 RESTful / 无状态架构中听起来不好,但实际上状态对于实现实际安全是必需的。没有状态,如果令牌被泄露,我们将无能为力。实际上,CSRF 令牌的大小非常小,对我们的架构影响应该可以忽略不计。

注销

添加 CSRF 将更新 LogoutFilter,使其仅使用 HTTP POST 方法。这确保了注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。

一种方法是使用表单进行注销。如果您确实想要一个链接,可以使用 JavaScript 让链接执行 POST(例如,在隐藏的表单上)。对于禁用 JavaScript 的浏览器,您可以选择让链接将用户带到一个注销确认页面,该页面将执行 POST。

HiddenHttpMethodFilter

HiddenHttpMethodFilter 应放置在 Spring Security 过滤器之前。通常情况下确实如此,但在防范 CSRF 攻击时,它可能会产生额外的影响。

请注意,HiddenHttpMethodFilter 只在 POST 请求中覆盖 HTTP 方法,因此这实际上不太可能导致任何实际问题。然而,最佳实践仍然是确保它放置在 Spring Security 的过滤器之前。

覆盖默认设置

Spring Security 的目标是提供默认设置,以保护您的用户免受攻击。这并不意味着您必须接受其所有默认设置。

例如,您可以提供自定义的 CsrfTokenRepository,以覆盖 CsrfToken 的存储方式。

您还可以指定自定义的 RequestMatcher 来确定哪些请求受 CSRF 保护(例如,您可能不关心注销是否被攻击)。简而言之,如果 Spring Security 的 CSRF 防护行为与您期望的不完全一致,您可以自定义其行为。

结论

您现在应该对 CSRF 是什么以及如何使用 Spring Security 保护您的应用程序免受 CSRF 攻击有了很好的了解。

下一篇文章中,我将讨论如何使用 Spring Security 的头部支持来保护您的应用程序免受点击劫持等攻击。

订阅 Spring 资讯

订阅 Spring 资讯,保持联系

订阅

抢先一步

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部