使用 CSRF 保护测试 scala Play (2.2.1) 控制器

Posted

技术标签:

【中文标题】使用 CSRF 保护测试 scala Play (2.2.1) 控制器【英文标题】:Testing scala Play (2.2.1) controllers with CSRF protection 【发布时间】:2013-11-19 05:46:18 【问题描述】:

我在测试使用 Play 的 CSRF 保护的控制器时遇到了一些问题。为了证明这一点,我创建了一个非常简单的 Play 应用程序,该应用程序几乎不会出现问题。

https://github.com/adamnfish/csrftest

完整的详细信息在该存储库的 README 中,但在这里总结一下:

考虑一个设计用于处理表单提交的控制器。它有一个使用 CSRFAddToken 的 GET 方法和一个使用 CSRFCheck 的 POST 方法。前者将 CSRF Token 添加到请求中,以便可以将包含有效令牌的表单字段放入呈现的视图中。当提交该表单时,如果 CSRF 检查通过并且提交有效,则会发生其他事情(通常是重定向)。如果表单提交无效,则会重新显示表单提交以及任何错误,以便用户可以更正表单并再次提交。

这很好用!

但是,在测试中我们现在遇到了一些问题。要测试控制器,您可以在测试中向它传递一个虚假请求。可以通过将 nocheck 标头添加到假请求来跳过 CSRF 检查本身,但由于没有可用于生成表单字段的令牌,因此无法呈现视图。测试失败,出现 RuntimeException,“Missing CSRF Token (csrf.scala:51)”。

鉴于它在实际运行但不在测试中时有效,看来这一定是 FakeRequests 在 Play 测试中运行的方式存在问题,但我可能做错了什么。我已经实现了http://www.playframework.com/documentation/2.2.1/ScalaCsrf 中描述的 CSRF 保护和http://www.playframework.com/documentation/2.2.1/ScalaFunctionalTest 中描述的测试。如果有人设法测试受 CSRF 保护的表单,我将不胜感激。

【问题讨论】:

【参考方案1】:

一种解决方案是使用浏览器进行测试,例如 Fluentlenium,因为这将管理 cookie 等,因此 CSRF 保护应该都能正常工作。

另一种解决方案是在 FakeRequest 中添加一个会话,使其包含一个令牌,例如:

FakeRequest().withSession("csrfToken" -> CSRF.SignedTokenProvider.generateToken)

显然,如果你经常这样做,你可以创建一个帮助方法来为你做这件事。

【讨论】:

我正在使用模拟和依赖注入直接测试控制器,因此这里不能选择硒风格的集成测试。向会话添加一个有效的、生成的令牌确实会阻止错误的发生。非常感谢!我仍然觉得这揭示了 Play 运行测试的方式中的一个错误,但我可以将它放在邮件列表中。再次感谢。 有没有办法在 Java 中做到这一点?【参考方案2】:

对于那些对 Java 感兴趣的人的奖励答案:我通过添加使其在 Play Framework 2.2 的 Java 版本中工作

.withSession(CSRF.TokenName(), CSRFFilter.apply$default$5().generateToken())

fakeRequest()

【讨论】:

感谢分享。我不喜欢 Java 版本的 Play 进行测试,所以我会相信你的话 :-)【参考方案3】:

从@plade 开始,我在我的基础测试类中添加了一个辅助方法:

protected static FakeRequest csrfRequest(String method, String url) 
    String token = CSRFFilter.apply$default$5().generateToken();
    return fakeRequest(method, url + "?csrfToken=" + token)
        .withSession(CSRF.TokenName(), token);

【讨论】:

玩 2.5.X 你需要使用CSRFFilter.apply$default$3().generateToken();CSRFFilter.apply$default$1().tokenName()【参考方案4】:

致那些仍然感兴趣的人:我设法通过在测试中启用 CSRF 保护来解决这个问题。然后,应用程序将为每个不包含令牌的请求创建一个令牌。查看我对this question的回复

【讨论】:

【参考方案5】:

对于那些可能感兴趣的人,我为 play 2.5.x 创建了一个 trait: https://***.com/a/40259536/3894835

然后您可以在您的测试请求中使用它,例如控制器的 addToken:

val fakeRequest = addToken(FakeRequest(/* params */))

【讨论】:

【参考方案6】:

我在我的基础集成测试类中使用以下方法:

def csrfRequest(method: String, uri: String)(implicit app: Application): FakeRequest[AnyContentAsEmpty.type] = 
  val tokenProvider: TokenProvider = app.injector.instanceOf[TokenProvider]
  val csrfTags = Map(Token.NameRequestTag -> "csrfToken", Token.RequestTag -> tokenProvider.generateToken)
  FakeRequest(method, uri, FakeHeaders(), AnyContentAsEmpty, tags = csrfTags)

然后你可以在你将使用FakeRequest的测试中使用它。

【讨论】:

以上是关于使用 CSRF 保护测试 scala Play (2.2.1) 控制器的主要内容,如果未能解决你的问题,请参考以下文章

Play 2.2:使用 Play Caching (Scala) 对代码进行单元测试时出现问题

使用ScalaTest和Selenium测试Scala.JS + Play

在 Play 2.4 scala 中禁用单个测试

为啥在使用“sbt it:test”时不执行 Play/Scala 项目中的集成测试?

使用 H2 内存数据库设置 Scala Play 测试

6种方法绕过CSRF保护