LoginSignup
3
1

More than 5 years have passed since last update.

Play2-authを使ってみる - 認可/Authorization編

Last updated at Posted at 2016-08-13

概要

前回の記事 (Play2-authを使ってみる - 認証/Authentication編)では認証・認可のざっくりした話とplay2-authと使って認証まで試してみました。
この記事では次の認可をやってみます。

(ここでは、ユーザーに既に付与されているはずの権限に対しての権限制御が上手く行っていることを確認します。)

また、せっかくなのでテストも書いてみましょう。

この記事のゴール

  • 認証・認可の大まかな流れを理解する
  • play2-authで認可部分まで作り動作確認する
    • ログインしたユーザーIDが正しいことを確認
    • ユーザーIDに付与された権限が与えられていることの確認
  • Play2-authを実装したControllerのテスト

環境

Play 2.5
play2-auth 0.14.2

前回の記事の続きからになります。

また、stackable-controllerを用いているので、以下の記事を読んでおくと理解が早いかもしれません。
Play2のstackable-controllerを使ってみる

全体のコードは以下。
https://github.com/uryyyyyyy/play2sample/tree/play2-auth-authorize

実装の流れ

  • (ServiceをDIできるようにする)
  • 認可用のコントローラを作る
  • 権限制御を用意する
  • 動作確認する
  • テストを書く

という流れになります。

ServiceをDIできるようにする

ここでのコードは本筋とそれるので省略します。

詳しくはコードとこちらの記事を見て下さい。
Play2のGuice DIを使う&テストも書く

認可用のコントローラを作る

認可用のコントローラを作るには、AuthElement と前回作った AuthConfigImpl を継承する必要があります。

そして、Actionの代わりにStackAction(AsyncStack)を使うことで権限チェックが出来ます。

AuthorizeController.scala
import javax.inject.{Inject, Singleton}

import akka.actor.ActorSystem
import jp.t2v.lab.play2.auth.AuthElement
import play.api.mvc.{Controller, Result}
import utils.{Administrator, AuthService, MyUser, NormalUser}

import scala.concurrent.{ExecutionContext, Future}

@Singleton
class AuthorizeController @Inject() (actorSystem: ActorSystem,
  val authService: AuthService) extends Controller with AuthElement with AuthConfigImpl {

  implicit val myExecutionContext: ExecutionContext = actorSystem.dispatcher

  def checkAdminRole = AsyncStack(AuthorityKey -> Administrator) { implicit request =>
    val user = loggedIn
    returnUser(user)
  }

  def returnUser(user:MyUser): Future[Result] ={
    Future(Ok("id: " + user.id))
  }

  def checkNormalRole = AsyncStack(AuthorityKey -> NormalUser) { implicit request =>
    val user = loggedIn
    Future(Ok("id: " + user.id))
  }

}

AsyncStack(AuthorityKey -> Administrator)の箇所で、このAPIにはAdministrator権限が必要なことを示しています。
同様にAuthorityKey -> NormalUserのAPIはNormal権限を持っていることになります。

また、AuthElementに付いているloggedInというメソッドを呼び出すことでログイン済みのユーザー情報を取得することが出来ます。この機能はstackable-controllerによって提供されています。
この機能を使って、今ログインしているユーザーを確認してみることも一緒にやってみます。
(ちなみに AuthElement 以外にもOptionを返すelementもついてたりするので、必要に応じて使い分けられます。)

また、routesに以下を追加しましょう。
後ほど、このAPIを叩いてみて権限制御されていることをチェックします。

GET     /authorize/admin              controllers.auth.AuthorizeController.checkAdminRole
GET     /authorize/normal              controllers.auth.AuthorizeController.checkNormalRole

権限制御を用意する

さて、上記だけでも動作するのですが、前回の記事でauthorizeメソッドが常にtrueを返すようにしているため、どの権限のユーザーでも全ての操作が出来てしまいます。

そこで、

  • Admin権限の操作はAdminユーザーのみができる
  • Normal権限の操作はAdmin/Normalのユーザーができる

というようにauthorizeメソッドの挙動を変えてみます。

AuthConfigImpl.scala
  override def authorize(user: User, authority: Authority)(implicit context: ExecutionContext): Future[Boolean] = {
    Future.successful(
      (user.role, authority) match {
        case (Administrator, _)       => true // AdminならどんなActionでも全権限を開放
        case (NormalUser, NormalUser) => true // ユーザがNormalUserで、ActionがNormalUserなら権限あり。もしActionがAdminだけなら権限なしになる。
        case _                        => false
      }
    )
  }

tupleの左がユーザーの権限、右が操作に対しての権限と理解しています。
(右と左の型を変えることもできそう?)

動作確認する

cookieが有効になっているブラウザで試して下さい。

Normalユーザーでログイン

まず、認証されていないとAPIを叩けないことを確認します。
念のためcookieをresetした上で、以下を叩きます。

http://localhost:9000/authorize/normal

まだ認証が済んでいないため、authenticationFailedにて定義されている「Unauthorized」が返ってきます。

さて、以下を叩いてログインを行います。

http://localhost:9000/authentication/login/normal/pass2

「login success」が返ると、normalユーザーでログインできたことを意味します。

次に、もう一度以下のAPIにアクセスします。

http://localhost:9000/authorize/normal

すると、「id: normal」が返ります。これは、このnormal権限のAPIへのアクセスが成功して、またログインしているユーザーIDがnormalであることを意味しています。

次に、Admin権限が必要なAPIを叩いてみます。

http://localhost:9000/authorize/admin

すると、authorizeメソッドでの権限チェックに失敗して、authorizationFailedで定義された「No permission」が返ってきます。

normal権限のユーザーではAdmin権限のAPIを叩けないことがわかりました。

Adminユーザーでログイン

まず、認証されていないとAPIを叩けないことを確認します。
念のためcookieをresetした上で、以下を叩きます。

http://localhost:9000/authorize/normal

まだ認証が済んでいないため、authenticationFailedにて定義されている「Unauthorized」が返ってきます。

さて、以下を叩いてログインを行います。

http://localhost:9000/authentication/login/admin/pass1

「login success」が返ると、adminユーザーでログインできたことを意味します。

次に、もう一度以下のAPIにアクセスします。

http://localhost:9000/authorize/normal

すると、「id: admin」が返ります。これは、このnormal権限のAPIへのアクセスが成功して、またログインしているユーザーIDがadminであることを意味しています。

次に、Admin権限が必要なAPIを叩いてみます。

http://localhost:9000/authorize/admin

今度はAPIを叩くことに成功して、再び「id: admin」が返ります。
先ほどは権限がなかったAdmin権限のAPIを叩けるようになっていることが確認できました。

テストを書く

最後にテストを書きます。

sontrollerのテストであれば、E2Eというか入り口から全部チェックすべきなのですが、

  • play2-authがplay Applicationに依存してる部分があってテストしにくい
  • 今回はidContainerやtokenAccessorをDIできていない

ので、全体のテストは諦めることにします。
(うまくDIできれば、controllerのコンストラクタDIでMockを挟むことでテストしやすくなるかもしれません。)

ここでは、権限チェックを通過した後のメソッド returnUser だけテストします。

AuthorizeControllerTest
import java.util.concurrent.TimeUnit

import akka.actor.ActorSystem
import akka.util.Timeout
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FunSpec, MustMatchers}
import play.api.test.Helpers
import utils.{Administrator, MyUser}

class AuthorizeControllerTest extends FunSpec with MustMatchers with MockitoSugar  {

  describe("AuthorizeControllerTest") {

    implicit val timeout = Timeout(5000, TimeUnit.MILLISECONDS)

    it("returnUser"){

      val actorSystem = ActorSystem.apply()
      val controller = new AuthorizeController(actorSystem, null)
      val user = MyUser("admin", "pass", Administrator)
      val result = controller.returnUser(user)

      Helpers.contentAsString(result) mustBe "id: admin"
      Helpers.status(result) mustBe 200
    }
  }
}

見て分かるとおり、普通のcontrollerのテストと同じようにできます(当たり前か。。)

まとめ

play2-authを使って、認証と認可(権限チェック)を見てみました。
少々長くなってとっちらかりましたが、そもそもの仕組みが難しいわりには理解しやすい実装をできているなと感じました。

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