LoginSignup
18
19

More than 5 years have passed since last update.

Play Framework で Basic 認証を行うアクションのミックスイン

Posted at

コントローラにミックスインして、 AdminAction を使って処理を書くことで、認証されているときにのみ画面を表示することができます。

ユーザー名とパスワードは Basic 認証で行われ、 AdminAction から与えられた request.userName で、認証された名前が得られます。

AdminSecure という名前ですが、管理者に限っているということではなく、Basic 認証を行っているだけです。
コントローラごとに認証される名前とパスワードを変えたいときは checkAccount をオーバーライドしてください。

AdminAction.scala
package controllers

import play.api._
import play.api.mvc._

/**
 * 管理者のアカウントを取り扱うモジュール
 */
trait AdminSecure {
  import play.api.mvc.Results._

  private[this] val accounts = Map("admin" -> "password")

  protected val basicRealm = "(my realm)"

  /** ユーザー名とパスワードが一致するかを返します */
  protected def checkAccount(userName: String, password: String) =
    accounts.get(userName).filter(_ == password).nonEmpty


  /**
   * 認証を確認して実行するアクション
   */
  object AdminAction {
    private val AUTHORIZATION = "authorization"
    private val WWW_AUTHENTICATE = "WWW-Authenticate"

    private def realm = "Basic realm=\"%s\"".format(basicRealm)

    def apply(f: AuthenticatedRequest[AnyContent] => Result): Action[AnyContent] =
      apply(BodyParsers.parse.anyContent)(f)

    def apply[A](p: BodyParser[A])(f: AuthenticatedRequest[A] => Result) = Action(p) { request =>
      request.headers.get(AUTHORIZATION) match {
        case Some(BasicAuthorization(name, pw)) if checkAccount(name, pw) =>
          f(new AuthenticatedRequest(name, request))
        case _ => Unauthorized("need admin login").withHeaders(WWW_AUTHENTICATE -> realm)
      }
    }

    /**
     * Basic 認証の値の取り扱い
     */
    private object BasicAuthorization {
      private val basicPefix = "Basic "
      private val AUTHORIZATION_PARAMS = """([^:]+?):(.+)""".r

      /**
       * Base64 エンコードで文字列化された Basic 認証値を、ユーザー名とパスワードに分解して返します
       *
       * @param auth Base64 エンコード文字列
       * @return 名前とパスワード
       */
      private[this] def decode(auth: String): Option[(String, String)] = {
        val d = javax.xml.bind.DatatypeConverter.parseBase64Binary(auth)

        new String(d, "utf-8") match {
          case AUTHORIZATION_PARAMS(username, password) => Some(username, password)
          case _ => None
        }
      }

      /**
       * WWW-Authenticate ヘッダの値から、ユーザー名とパスワードを取り出します。
       *
       * @param authorization WWW-Authenticate ヘッダ値。ヘッダの名前は含まない。
       * @return 名前とパスワード
       */
      def unapply(authorization: String): Option[(String, String)] = authorization match {
        case s if s startsWith basicPefix => decode(s drop basicPefix.length)
        case _ => None
      }
    }

    /** 認証されて実行するリクエスト */
    class AuthenticatedRequest[A] private[controllers]
      (val userName: String, request: Request[A]) extends WrappedRequest(request)
  }
}

実装方法

Application.scala
package controllers

import play.api._
import play.api.mvc._

object Application extends Controller with AdminSecure {
  def adminHome = AdminAction { implicit request =>
    Ok("Welcome back, " + request.userName)
  }
}
18
19
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
18
19