Playframework で CSRF エラーを制御する

この記事は Play framework Advent Calendar 2014 - Adventar の 11 日目です。

10 日目は xuwei さんの 2014年のplayframeworkと自分のpull request でした。

Playframework では CSRF 対策が提供されています。

https://www.playframework.com/documentation/ja/2.3.x/ScalaCsrf

ただ実際に CSRF トークンエラーを発生させてみると、以下のような英文でテキスト文字列が出るだけのもので、ユーザーにとって次のアクションが取りづらいものになってしまっています。

f:id:ussy00:20141211002505p:plain

実際に自分で使おうとしたときに困ってしまったので、なんとか PR を出して自分で制御ができるようにしてもらいましたので、今日はこの場を借りて使い方を説明したいと思います。

CSRF エラーを制御するためには CSRFFiltererrorHandler 引数に CSRF.ErrorHandler trait を実装したクラスを定義すれば行えるようになってます。

今回は Playframework のエラーハンドリングの仕組みを利用するため、自分で例外クラスも定義して throw するようにしておきます。

import play.filters.csrf._

class CSRFTokenException(message: String) extends Exception(message: String)

object CSRFErrorHandler extends CSRF.ErrorHandler {
  def handle(request: RequestHeader, message: String) = throw new CSRFTokenException(message)
}

Controller 側では CSRFFilter を利用する箇所で、先ほど自分で定義したクラスを渡してあげます。

package controllers

import play.api._
import play.api.mvc._
import play.filters.csrf._

object Application extends Controller {

  def index = CSRFFilter(errorHandler = CSRFErrorHandler) { Action { implicit request =>
    Ok(views.html.index())
  }}

  def post = CSRFFilter(errorHandler = CSRFErrorHandler) { Action { implicit request =>
    Ok("nice post.")
  }}

}

続いてエラーハンドリングをするために Global オブジェクトを定義して、投げられてくる例外を抜き出して CSRFTokenException だったときの処理を書きます。

https://www.playframework.com/documentation/ja/2.3.x/ScalaGlobal

import play.api._
import play.api.mvc._
import play.api.mvc.Results._
import scala.concurrent.Future
import controllers._

object Global extends GlobalSettings {

  override def onError(request: RequestHeader, ex: Throwable) = {
    ex.getCause match {
      case ex: CSRFTokenException =>
        // signout
        Future.successful(Redirect(routes.Signin.index))
      case _ =>
        Future.successful(InternalServerError(views.html.error(ex)))
    }
  }
}

この例では Rails のようにログインページにリダイレクトさせてみました。

なお View はドキュメントにあるとおり CSRF トークンを埋め込む感じになります。

@()(implicit request: RequestHeader)

@import helper._

<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    @form(routes.Application.post) {
      @CSRF.formField
      <input type="submit" value="post">
    }
  </body>
</html>

いかがでしたでしょうか? ErrorHandler trait を実装して上げることで、柔軟に CSRF のエラーハンドリングが行えるようになりました。

今回は Scala による使い方を説明しましたが Java でもアノテーションでハンドリング用の class を指定できるようになっています。

Java の Playframework ユーザーの方も是非試してみてください。