play2-authの認可設定部分のカスタマイズ(自分の場合)


今回はplay2-authの認可の話です。play2-authのとある機能に非常に感心したので書きます。

play2-authとは

t2v/play2-auth · GitHub

play2-authとはPlay frameworkのための認証認可ライブラリです。@gakuzzzzさんが開発されています。

使い方はGitHubを見れば大体分かるかと思います。半年前に導入したときはScala初心者でしたし、意外とplay2-auth用に書かなくてはならないコードがあるので苦労したのですが・・・(今回は導入がメインではないのでそこは話しません)

実はめちゃくちゃ嬉しいことに日本語訳のREADMEが用意されています。これにはかなり助けられました。

https://github.com/t2v/play2-auth/blob/master/README.ja.md

このplay2-authを使うと何ができるかというと

認証(idとpassで正規ユーザと認めるかどうか) と

認可(このidのユーザに対してこの操作を認めるかどうか)を行うことができるようになります。

play2-authの認可の仕組み

大体play2-authについて説明しましたのでここから本題の認可の話に移ります。

さて認可は簡単に言ってしまうとユーザに対する操作の許可、な訳ですが、play2-authではこれをAuthority型と(AuthorityKey -> Hoge)属性とdef authorizeで実現しています。

サンプルでは以下のようになっています。

  // ※部分的に抜き出しています

  type Authority = Role

  def authorize(user: User, authority: Authority)(implicit ctx: ExecutionContext): Future[Boolean] = Future.successful {
    (user.role, authority) match {
      case (Administrator, _) => true
      case (NormalUser, NormalUser) => true
      case _ => false
    }
  }

  def main = StackAction(AuthorityKey -> NormalUser) { implicit request =>

Authorityにはユーザで独自の型を与えるようになっています。authorizeは自分で好きな権限チェック処理を実現します。最後にコントローラの関数を定義する部分でAuthorityKeyを設定しています。

authorizeはコントローラの関数が動くタイミングで動き、authority変数にAuthorityKeyの値が入ってきます。つまり上記の例だとNormalUserが入ってくる訳です。

上記のチェックはログイン中ユーザの権限が Administrator(Role型です)だったら問答無用で許可、 NormalUserだったらAuthorityKeyがNormalUserだった場合のみ許可、 ユーザの権限がその他だった場合は問答無用で不許可、としています。

実際に遭遇した権限問題

当初はこのサンプル通りで全く問題が無かったのですが、そういう訳にはいかない事情が発生しました。

顧客「ローンチ手前ですが残念なお知らせがあります。現状のアカウント方式では問題があることが判明しました。権限を2種から4種に変更します。」

( ゚д゚)

顧客「画面1は権限Aと権限Bだけ許可してください。画面2は権限CとDだけ、画面3は権限AとCだけ、(略)」

(;゚д゚)

以下が最終的に確定した要件です。 権限:SystemAdmin, SystemNormal, Admin, Normal 画面1の許可権限:SystemAdmin, SystemNormal 画面2の許可権限:Admin, Normal 画面3の許可権限:SystemAdmin, Admin

この要求を満たす為にはサンプルのような方式では上手く行きません。

play2-auth改造プロジェクトか・・・と思いきや実は簡単に解決できた

上記の要求を満たす為にplay2-authを改造しなくちゃならんのか・・・と思っていたのですが、よく考えたら不要でした。

「def authorizeとAuthorityは独自に定義可能なので、ここを変えればいいじゃん!」

def authorizeをライブラリ利用者側で実装するのはまあ当然ですが、Authorityまでもライブラリ利用側で定義するというこの設計は本当に賢いな!と思いました。

以下が自分が書いたコードです。

  // ※部分的に抜き出しています

  type Authority = Seq[Role]

  def authorize(user: User, authority: Authority)(implicit ctx: ExecutionContext): Future[Boolean] = Future.successful {
    authority.contains(user.role)
  }


  def screen1 = StackAction(AuthorityKey -> (SystemAdmin :: SystemNormal :: Nil)) { implicit request =>
  def screen2 = StackAction(AuthorityKey -> (Admin :: Normal :: Nil)) { implicit request =>
  def screen3 = StackAction(AuthorityKey -> (SystemAdmin :: Admin :: Nil)) { implicit request =>

 

という感じで無事に解決できました。play2-authのAuthorityあっぱれですね!

まとめ

play2-authで認証・認可が実現できます。

実装はサンプル見ましょう。https://github.com/t2v/play2-auth/blob/master/README.ja.md

サンプルの認可方式でやってたけど、認可要件が変わったので、Authorityの型を変えることで対応しました。