文档

§防止跨站点请求伪造

跨站点请求伪造 (CSRF) 是一种安全漏洞,攻击者利用它欺骗受害者的浏览器使用受害者的会话发出请求。由于会话令牌会随每个请求一起发送,如果攻击者可以强制受害者的浏览器代表他们发出请求,攻击者就可以代表用户发出请求。

建议您熟悉 CSRF,了解攻击向量是什么以及攻击向量不是什么。我们建议您从 OWASP 的此信息 开始。

对于哪些请求是安全的以及哪些容易受到 CSRF 请求的攻击,没有简单的答案,原因是对于插件和未来扩展规范允许的内容没有明确的规范。从历史上看,浏览器插件和扩展程序放宽了框架以前认为可以信任的规则,从而导致许多应用程序出现 CSRF 漏洞,而框架则需要负责修复这些漏洞。出于这个原因,Play 在其默认设置中采取了保守的方法,但允许您配置何时进行检查。默认情况下,Play 将在以下所有条件都为真时要求进行 CSRF 检查

注意:如果您使用基于浏览器的身份验证,而不是使用 cookie 或 HTTP 身份验证(例如 NTLM 或基于客户端证书的身份验证),那么您必须设置 play.filters.csrf.header.protectHeaders = null,这将保护所有请求,或者将身份验证中使用的标头包含在 protectHeaders 中。

§Play 的 CSRF 防护

Play 支持多种方法来验证请求是否为 CSRF 请求。主要机制是 CSRF 令牌。此令牌放置在提交的每个表单的查询字符串或主体中,并且也放置在用户的会话中。然后,Play 验证这两个令牌是否存在并匹配。

为了允许对非浏览器请求进行简单的保护,Play 默认情况下会检查带有 CookieAuthorization 标头的请求。您可以配置 play.filters.csrf.header.protectHeaders 来定义必须存在的标头才能执行 CSRF 检查。如果您使用 AJAX 发出请求,则可以将 CSRF 令牌放置在 HTML 页面中,然后使用 Csrf-Token 标头将其添加到请求中。

或者,您可以设置 play.filters.csrf.header.bypassHeaders 来匹配常见的标头:常见的配置是

此配置将如下所示

play.filters.csrf.header.bypassHeaders {
  X-Requested-With = "*"
  Csrf-Token = "nocheck"
}

使用此配置选项时应谨慎,因为历史上浏览器插件破坏了这种类型的 CSRF 防御。

§信任 CORS 请求

默认情况下,如果您在 CSRF 过滤器之前有一个 CORS 过滤器,则 CSRF 过滤器将允许来自受信任来源的 CORS 请求通过。要禁用此检查,请设置配置选项 play.filters.csrf.bypassCorsTrustedOrigins = false

§应用全局 CSRF 过滤器

注意:从 Play 2.6.x 开始,CSRF 过滤器包含在 Play 的默认过滤器列表中,这些过滤器会自动应用于项目。有关更多信息,请参见 过滤器页面

Play 提供了一个全局 CSRF 过滤器,可以将其应用于所有请求。这是向应用程序添加 CSRF 防护的最简单方法。要手动添加过滤器,请将其添加到 application.conf

play.filters.enabled += "play.filters.csrf.CSRFFilter"

也可以在路由文件中为特定路由禁用 CSRF 过滤器。为此,请在路由之前添加 nocsrf 修饰符标签

+ nocsrf
POST  /api/new              controllers.Api.newThing()

§获取当前令牌

可以使用 CSRF.getToken 方法访问当前的 CSRF 令牌。它需要一个 RequestHeader

Optional<CSRF.Token> token = CSRF.getToken(request);

注意:如果安装了 CSRF 过滤器,Play 会尝试避免生成令牌,只要使用的 cookie 是 HttpOnly(这意味着它无法从 JavaScript 访问)。当发送具有严格正文的响应时,Play 会跳过将令牌添加到响应中,除非已经调用了 CSRF.getToken。这对于不需要 CSRF 令牌的响应来说,可以显著提高性能。如果 cookie 未配置为 HttpOnly,Play 会假设您希望从 JavaScript 访问它,并无论如何都会生成它。

为了帮助将 CSRF 令牌添加到表单中,Play 提供了一些模板助手。第一个将它添加到操作 URL 的查询字符串中

@import helper._

@form(CSRF(scalaguide.forms.csrf.routes.ItemsController.save())) {
    ...
}

这可能会渲染一个看起来像这样的表单

<form method="POST" action="/items?csrfToken=1234567890abcdef">
   ...
</form>

如果在查询字符串中使用令牌不可取,Play 还提供了一个助手,用于将 CSRF 令牌作为隐藏字段添加到表单中

@form(scalaguide.forms.csrf.routes.ItemsController.save()) {
    @CSRF.formField
    ...
}

这可能会渲染一个看起来像这样的表单

<form method="POST" action="/items">
   <input type="hidden" name="csrfToken" value="1234567890abcdef"/>
   ...
</form>

§将 CSRF 令牌添加到会话中

为了确保 CSRF 令牌可用于在表单中渲染并发送回客户端,全局过滤器将为所有接受 HTML 的 GET 请求生成一个新令牌,如果传入请求中还没有令牌。

§按操作方式应用 CSRF 过滤

有时全局 CSRF 过滤可能不合适,例如在应用程序可能希望允许某些跨域表单发布的情况下。一些非基于会话的标准,例如 OpenID 2.0,需要使用跨站点表单发布,或在服务器到服务器的 RPC 通信中使用表单提交。

在这些情况下,Play 提供了两个可以与您的应用程序操作组合在一起的操作。

第一个操作是 play.filters.csrf.RequireCSRFCheck 操作,它执行 CSRF 检查。它应该添加到所有接受会话身份验证的 POST 表单提交的操作中

@RequireCSRFCheck
public Result save() {
  // Handle body
  return ok();
}

第二个操作是 play.filters.csrf.AddCSRFToken 操作,它会在传入请求中不存在 CSRF 令牌时生成一个。它应该被添加到所有渲染表单的操作中。

@AddCSRFToken
public Result get(Http.Request request) {
  return ok(CSRF.getToken(request).map(CSRF.Token::value).orElse("no token"));
}

§CSRF 配置选项

完整的 CSRF 配置选项可以在过滤器 reference.conf 中找到。一些例子包括

§测试 CSRF

在功能测试中,如果您正在渲染带有 CSRF 令牌的 Twirl 模板,则需要有一个可用的 CSRF 令牌。您可以通过在 play.mvc.Http.RequestBuilder 实例上调用 play.api.test.CSRFTokenHelper.addCSRFToken 来实现。

Http.RequestBuilder request = new Http.RequestBuilder().method(POST).uri("/xx/Kiwi");

request = CSRFTokenHelper.addCSRFToken(request);

下一步: 使用表单模板助手


发现此文档中的错误?此页面的源代码可以在 这里 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。