Play framework and Authorization

Play framework and Authorization

Recently, I need to write a authorization solution for play framework. From my understanding, it is just a filter to protect the resources of all my actions in controllers.

I have done this for Spary framework. This time, we are facing play framework. It takes me sometime to figure this out.

First of all, I am using play framework 2.4.3 version.

I am using configuration file, but actually we should use Redis or other storage. But I only want to show how the filter works among the actions and controllers.
apigateway {
  mapping=[
    { "key1" : "campaignId1" }
    { "key1" : "campaignId2" }
  ]
}

Here is how I read this configuration map.
  val api_gateway_mapping: Map[String, Seq[String]]= {
    val list:Iterable[ConfigObject] = config.getObjectList("apigateway.mapping").asScala

    val result = (
      for {
        item:ConfigObject <- list
        entry : Entry[String, ConfigValue] <- item.entrySet().asScala
        key = entry.getKey
        value = entry.getValue.unwrapped().toString
      } yield (key, value)).groupBy(record => record._1 ) map { item =>
      item._1 -> {
        item._2.toSeq map { value =>
          value._2
        }
      }
    }
    result
  }

2 Important models
package com.sillycat.services.auth
import play.api.mvc.WrappedRequest
class ResourceRequest[A](val campaigns: Seq[String], val campaignID:String, request: TokenRequest[A]) extends WrappedRequest[A](request) {
  def token = request.token
}

package com.sillycat.services.auth
import play.api.mvc._
class TokenRequest[A](val token: Option[String], request: Request[A]) extends WrappedRequest[A](request)

These requests are hosting more parameters, like token, campaignID, resources and etc.

Actions to Auth
what we will do in these actions, first, get the token from headers ——> fetch the mapping based on the token ——> get the resources from that request ——> match the mapping with the resources from request

#1 Get token from Headers
package com.sillycat.services.auth
import com.sillycat.utils.IncludeLogger
import play.api.mvc.{Request, ActionTransformer, ActionBuilder}
import scala.concurrent.Future

object TokenFetchAction
  extends ActionBuilder[TokenRequest]
  with ActionTransformer[Request, TokenRequest]
  with IncludeLogger
{
  def transform[A](request: Request[A]) = Future.successful {
    val xToken = request.headers.get("x-api-key")
    logger.trace("TokenFetchAction system gets token = " + xToken)
    new TokenRequest(xToken, request)
  }
}

#2 Fetch the Mapping
package com.sillycat.services.auth

object AuthorizeService
  extends IncludeAuthConfig
{
  /**
    * no guava cache, no expire time, this is a just temperary solution
    */
  val memoryCache:Map[String, Seq[String]] = api_gateway_mapping

  def getResourceMappingByToken(xToken:String):Option[Seq[String]] = {
    val resources = memoryCache.get(xToken)
    resources
  }

}

#3 Get resources from requests
package com.sillycat.services.auth
import com.sillycat.utils.IncludeLogger
import play.api.mvc.{Results, ActionRefiner}
import scala.concurrent.Future

trait IncludeAuthService
  extends IncludeLogger
{

  def ResourceFetchAction(campaignID:String) = new ActionRefiner[TokenRequest, ResourceRequest] {
    def refine[A](request: TokenRequest[A]) = Future.successful {
      val xTokenOpt = request.token
      logger.trace("ResourceFetchAction get xTokenOpt = " + xTokenOpt)
      xTokenOpt match {
        case Some(xToken) => {
          logger.trace("ResourceFetchAction get xToken = " + xToken)
          val resourceOpt = AuthorizeService.getResourceMappingByToken(xToken)
          resourceOpt.map{ resources =>
            logger.trace("ResourceFetchAction get resources mapping = " + resources)
            logger.trace("ResourceFetchAction get campaignID = " + campaignID)
            new ResourceRequest(resources, campaignID, request)
          }.toRight{
            logger.error("ResourceFetchAction empty resources mapping data resourceOpt = " + resourceOpt)
            Results.Unauthorized
          }
        }
        case _ => {
          logger.error("System does not get x-api-key, bad request.")
          Left(Results.Unauthorized)
        }
      }
    }
  }

}

#4 Match the Mapping with resources
package com.sillycat.services.auth
import com.sillycat.utils.IncludeLogger
import play.api.mvc.{Results, ActionFilter}
import scala.concurrent.Future

object AuthCampaignCheckAction
  extends ActionFilter[ResourceRequest]
    with IncludeLogger
{
  def filter[A](request: ResourceRequest[A]) = Future.successful {
    val campaignID = request.campaignID
    val resources = request.campaigns

    logger.trace("AuthCampaignCheckAction campaignID = " + campaignID)
    logger.trace("AuthCampaignCheckAction resources = " + resources)

    if (resources.contains(campaignID)){
      logger.trace("AuthCampaignCheckAction, You are authorized!")
      None
    } else{
      logger.warn("AuthCampaignCheckAction, Unauthorized!")
      Some(Results.Unauthorized)
    }
  }
}

After that, when we wants to use them in the Controller.
…snip...
    with IncludeAuthService
..snip...
def addJob(
              @ApiParam(name="campaignID",value="campaign ID",defaultValue=“xxxxx",required=true,allowMultiple=false)
              @PathParam("campaignID")
              campaignID:String
            ) = (TokenFetchAction andThen ResourceFetchAction(campaignID) andThen AuthCampaignCheckAction)(BodyParsers.parse.json) { implicit request =>
…snip...

References:
https://www.playframework.com/documentation/2.4.x/ScalaActionsComposition

http://stackoverflow.com/questions/19868153/authorisation-check-in-controller-scala-play

猜你喜欢

转载自sillycat.iteye.com/blog/2286371