play-logo

さて次に、RESTful な CRUDサーバーを作ってみましょう。
と、その前に、POST リクエストで CSRF(クロスサイト・リクエストフォージェリ)トークンをチェックしてないので、CSRFの脆弱性対策からスタートします。

CSRF対策

CSRFの脆弱性対策から実装してみよう!

こちらのドキュメントを読むと、build.sbt のプロジェクトに Play filters helpers の依存関係を追加すると書いてあるので、build.sbt を以下のような設定に修正します。

  • build.sbt
name := "play-scala-intro"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "com.typesafe.play" %% "play-slick" % "2.0.2",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.2",
  "com.h2database" % "h2" % "1.4.190",
  specs2 % Test,
  filters
)

ここで、注意したいのが、"specs2 % Test" と "filters" の間に "," で区切られて、Seq(scala.collection.Seq) に追加されているのに注意しましょう。

追加すると、External Libraries にダウンロードされます。ダウンロードが終わる前に sbt を再起動してしまうと、うまくダウンロードされないので注意してください。

Shift を2回押して、CSRFFilter.scala で検索してもが見つからない場合は、一旦、IntelliJ を閉じて開くと build.sbt に追加した filters が External Libraries の filters-helpers_2.11-2.5.10-sources.jar として、ダウンロードされてきます。

22

app フォルダの下に以下のような Filters.scala ファイルを作成して、Filters クラスを実装してみましょう。

  • app/Filters.scala
import play.api.http.DefaultHttpFilters
import play.filters.csrf.CSRFFilter
import javax.inject.Inject

class Filters @Inject() (csrfFilter: CSRFFilter)
  extends DefaultHttpFilters(csrfFilter)

こちらを追加した状態で POST すると、CSRF の脆弱性がないか token をチェックされます。
いまは token がないので、POST ができなくなります。

Shift を2回押して、CSRF.scala で検索して開いてみましょう。

23

  • CSRF.scala
package views.html.helper

import play.api.mvc._
import play.twirl.api.{ Html, HtmlFormat }

/**
 * CSRF helper for Play calls
 */
object CSRF {

  /**
   * Add the CSRF token as a query String parameter to this reverse router request
   */
  def apply(call: Call)(implicit request: RequestHeader): Call = {
    val token = play.filters.csrf.CSRF.getToken.getOrElse(sys.error("No CSRF token present!"))
    new Call(
      call.method,
      s"${call.url}${if (call.url.contains("?")) "&" else "?"}${token.name}=${token.value}"
    )
  }

  /**
   * Render a CSRF form field token
   */
  def formField(implicit request: RequestHeader): Html = {
    val token = play.filters.csrf.CSRF.getToken.getOrElse(sys.error("No CSRF token present!"))
    // probably not possible for an attacker to XSS with a CSRF token, but just to be on the safe side...
    Html(s"""<input type="hidden" name="${token.name}" value="${HtmlFormat.escape(token.value)}"/>""")
  }

}

CSRF object は view.html.helper の下にあり、かつ index.scala.html ファイルに @import helper._ があれば、呼び出せます。
apply メソッドの第二引数に、"implicit request: RequestHeader" を必要としているので、
以下のように、app/views/index.scala.html を編集し、CSRF の token を埋め込むフォームを作ってみましょう。

  • app/views/index.scala.html
@(person: Form[CreatePersonForm])(implicit request: RequestHeader, messages: Messages)

@import helper._

@main("Welcome to Play") {
    <script type='text/javascript' src='@routes.Assets.versioned("javascripts/index.js")'></script>

    <ul id="persons"></ul>

  @form(CSRF(routes.PersonController.addPerson())) {
        @inputText(person("name"))
        @inputText(person("age"))

        <div class="buttons">
            <input type="submit" value="Add Person"/>
        </div>
    }
}

1行目が、

@(person: Form[CreatePersonForm])(implicit messages: Messages)

から

@(person: Form[CreatePersonForm])(implicit request: RequestHeader, messages: Messages)

に変わってること、

10行目が、

  @form(routes.PersonController.addPerson()) {

から

  @form(CSRF(routes.PersonController.addPerson())) {

に変わっていることに注意してください。

これで、Controller から、request: RequestHeader のパラメーターを渡さないといけなくなりました。
app/controllers/PersonController.scala も以下のように編集しましょう。

app/controllers/PersonController.scala のindex関数が、

変更前

  def index = Action {
    Ok(views.html.index(personForm))
  }

変更後

  def index = Action { implicit request =>
    Ok(views.html.index(personForm))
  }

と、Action のクロージャーに「implicit request =>」が追加変更されていれば OK 。

これでもう一度、stop の ■ をクリック、sbt run の ▶︎ をクリックし、"Apply this script!" をクリック、初期データを投入して、POST してみよう。POST できると以下のような画面になります。

24

サイトを右クリックして、ソースを表示したときに、

25

以下の部分に注目しましょう。

<form action="/person?csrfToken=ede42d0a12b272a21acd5d805032776c3b5c79f7-1485742555886-58de12b973a7859ed9421af5" method="POST" >

こちらを見ると、ワンタイムトークンが action に埋め込まれていることが確認できます。
ここまでできたら、git にコミットしよう!

git add -A
git commit -m "CSRFの脆弱性の対策の実装"

template ファイルは、app/views/index.scala.html を以下のように編集しても CSRF トークンが埋め込み可能です。

  @form(CSRF(routes.PersonController.addPerson())) {

から

  @form(routes.PersonController.addPerson()) {
    @CSRF.formField

に変更します。

  • app/views/index.scala.html
@(person: Form[CreatePersonForm])(implicit request: RequestHeader, messages: Messages)

@import helper._

@main("Welcome to Play") {
    <script type='text/javascript' src='@routes.Assets.versioned("javascripts/index.js")'></script>

    <ul id="persons"></ul>

  @form(routes.PersonController.addPerson()) {
        @CSRF.formField
        @inputText(person("name"))
        @inputText(person("age"))

        <div class="buttons">
            <input type="submit" value="Add Person"/>
        </div>
    }
}

26

以下の部分に注目しましょう。

</form><form action="/person" method="POST" >

        <input type="hidden" name="csrfToken" value="75fba6a201b58023a9260ea05d369efb0b688b83-1485742763921-58de12b973a7859ed9421af5"/>

〜略〜
</form>

このような感じで、CSRF のワンタイムトークンが埋め込まれてPOST しても チェックしてくれます。
ここまでできたら、git にコミットしましょう。

git add -A
git commit -m "CSRFの実装を変更"

その3に続きます。