LoginSignup
3
1

More than 1 year has passed since last update.

【Scala×Play Framework】Action Refinerを利用した認証機能

Posted at

はじめに

この記事では
Play Frameworkにおいて、Action Refiner を利用した認証機能の実装方法についてお伝えします。

大まかな手順

  1. Action Refinerを継承したアクションオブジェクトを作成する

  2. アクションオブジェクト内にrefineメソッドを定義する
    2-1. リクエストの属性に値を格納する

  3. Actionとアクションオブジェクトをチェーンで繋ぎ、コントローラーで使用する

Action Refiner とは

  • リクエストをすぐにインターセプトして結果(Result型の値)を返す
  • 新しいリクエスト(Request型の値)で処理を続行する

上記のどちらの処理を行うかを判別して、実行してくれます。

この特性を利用して、認証機能を実装することが出来ます。

定義元:https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L514

実装手順

今回の実装例としては、
ユーザーのログイン認証機能について実装していきます。

1.Action Refinerを継承したアクションオブジェクトを作成する

extendsで継承することができます。

▼実装コード

object UserAction extends ActionRefiner[Request, Request] {}

2.アクションオブジェクト内にrefineメソッドを定義する

Action Refinerトレイトに定義されているrefineメソッドを利用します。

refineメソッドは抽象メソッドの為、自分で定義して利用することで、Action Refinerの恩恵を受けることができます。
※ 戻り値がFuture[Either[Result, P[A]]]になるように定義する。

定義元:https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L522

▼実装コード

object UserAction extends ActionRefiner[Request, Request] {
  def refine[A](request: Request[A]): Future[Either[Result, P[A]]] = Future.successful {
    // ログインしている     => Right(newRequest)
    // ログインしていない  => Left(Results.Redirect("/login_form"))
    request.session.get("userId")
    .map(userId => {
      for {
        // userを取得する
        user <- UserRepository.findById(userId)
      } yield {
        // 取得したuserをリクエストの属性に格納する(後述で説明します)
        val newRequest: Request[A] = request.addAttr(UserAttr.User, user)
        Right(newRequest)
      }
    })
    .getOrElse(Future.successful(Left(Results.Redirect("/login_form"))))
  }
}

2-1.リクエストの属性に値を格納する

参考:https://www.playframework.com/documentation/2.8.x/Highlights26#Request-attributes

リクエストには値を書き込むことができます。
以下の手順で実装します。

2-1-1.属性KEYを定義・作成する

// リクエストの属性を格納する場所(キー)を作る
object UserAttrs {
  val User: TypedKey[User] = TypedKey.apply[User]("user")
}

TypedKey.apply[格納する型] => TypedKeyを作成する事ができる。

TypedKeyの名前は引数に渡した文字列となる。(上のコードで言うと"user"

定義元:https://github.com/playframework/playframework/blob/20bb90532fe30cf933daa544dd61e301e7dacbe2/core/play/src/main/scala/play/api/libs/typedmap/TypedKey.scala#L63

2-1-2.リクエストに属性KEYと値のセットを書き込む(リクエストに属性を付与する)

// リクエストのTypedMapに属性KEYと値のセットを格納する
// 取得したuserをリクエストの属性に格納する
val newRequest: Request[A] = request.addAttr(UserAttr.User, user)

定義元:https://www.playframework.com/documentation/2.8.4/api/scala/play/api/mvc/Request.html#addAttr[B](key:play.api.libs.typedmap.TypedKey[B],value:B):play.api.mvc.Request[A]

TypedMap => 「TypedKey」と「値」を格納する不変のマップ。

例)

// 型
TypedMap(TypedKey[String] -> String)

// キーのタイプ([]の部分)は、属性のタイプを示す
キーの型  TypedKey[String]
値の型     String

2-1-3.コントローラーでリクエストの属性値を取得する

// 属性値を取得する
newRequest.attrs(UserAttrs.User)

2-1-2. で書き込んだ値を取得する方法です。(後述のコントローラー側で使用します)

定義元:https://www.playframework.com/documentation/2.8.4/api/scala/play/api/mvc/Request.html#attrs:play.api.libs.typedmap.TypedMap

3.Actionとアクションオブジェクトをチェーンで繋ぎ、コントローラーで使用する

参考:https://www.playframework.com/documentation/ja/2.3.x/ScalaActionsComposition#%E3%81%B2%E3%81%A8%E3%81%BE%E3%81%A8%E3%82%81%E3%81%AB%E3%81%99%E3%82%8B

andThen を使ってアクション関数をつなぎ、1つのアクションを作成することができます。

▼実装コード

def view = (Action andThen UserAction) { request =>
  val user: User = request.attrs(UserAttrs.User)
    Ok("User " + user.name)
  }

『注意するポイント』

先頭のみAction Builderで始まるアクションにしましょう。

※ 上記の実装コードで言うと、Actionを指します。

Action Builderには

  • 関数合成のためのcompose処理 (これは他の種別にもある)
  • 非同期処理に必要なコンテクスト管理
  • リクエストをパースする処理

などが含まれており、

チェーンする後続定義として、

  • 非同期処理に必要なコンテクスト管理
  • リクエストをパースする処理

の定義機能を含むのは冗長であるからです。

そのような理由から、
Action Builder定義は、基本的には最初の1個目であり、
andThenで後に使うようには設計されていません。

まとめ

Action Refinerの使い方について紹介しました。
このように、Action Refinerを使用することで、認証機能を実装することができます。

Action Refiner以外にも、

というトレイトもありますので、ぜひ調べて使用してみて下さい。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1