Play过滤器中的基于令牌的身份验证&传递物体

时间:2014-10-31 13:16:15

标签: scala authentication playframework ebean

我已经编写了一个基于Play with Scala的API,我对结果非常满意。我正处于一个阶段,我正在考虑优化和重构下一版API的代码,我有几个问题,其中最紧迫的是身份验证和我管理身份验证的方式。 / p>

我写的产品与企业有关,所以在每个请求中公开用户名+密码,或者在服务器端维护会话都不是最好的选择。以下是我的应用程序验证的工作原理: 用户使用用户名/密码进行身份验证 服务器返回与用户关联的令牌(存储为用户表中的列) 从该点向服务器发出的每个请求都必须包含令牌。 用户注销时也会更改令牌,也会定期更改令牌。 现在,我的实现非常简单 - 我有几个表单用于所有API端点,每个端点都需要一个令牌来查找用户,然后评估是否允许用户在请求中进行更改,或获取数据。因此,经过身份验证的领域中的每个表单都是需要令牌的表单,然后是其他几个参数,具体取决于API端点。

这导致重复。我使用的每个表单都必须具有基于令牌的验证部分。它显然不是最简单的方法。我一直需要一遍又一遍地复制相同类型的代码。

我一直在阅读有关Play过滤器的内容并提出一些问题: 使用Play过滤器进行基于令牌的身份验证是个好主意 过滤器是否可以应用于某个请求路径? 如果我根据过滤器中提供的令牌查找用户,是否可以将查找的用户对象传递给操作,以便我们不会重复查找该请求? (参见我如何处理这种情况的例子。)

  case class ItemDelete(usrToken: String, id: Long) {
    var usr: User = null
    var item: Item = null
  }


  val itemDeleteForm = Form(
    mapping(
      "token" -> nonEmptyText,
      "id" -> longNumber
    ) (ItemDelete.apply)(ItemDelete.unapply)
    verifying("unauthorized",
      del => {
        del.usr = User.authenticateByToken(del.usrToken)
        del.usr match {
          case null => false
          case _ => true
        }
      }
    )
    verifying("no such item",
      del => {
        if (del.usr == null) false
        Item.find.where
          .eq("id", del.id)
          .eq("companyId", del.usr.companyId) // reusing the 'usr' object, avoiding multiple db lookups
        .findList.toList match {
          case Nil => false
          case List(item, _*) => {
            del.item = item
            true
          }
        }
      }
    )
  )

2 个答案:

答案 0 :(得分:3)

查看Action Composition,它允许您检查和转换操作请求。如果您使用播放过滤器,那么它将在每个请求上运行。

例如,您可以创建一个检查请求的TokenAction,如果找到了令牌,则优化请求以包含基于令牌的信息,例如用户。如果没有找到令牌,则返回另一个结果,如Unauthorized,Redirect或Forbidden。

我创建了一个SessionRequest,它具有一个带有可选登录用户的用户属性,它首先从数据库中查找现有会话,然后获取附加用户并将其传递给请求

如果没有可用的用户,则过滤器(WithUser)将拦截SessionRequest,然后将用户重定向到登录页面

// Request which optionally has a user
class SessionRequest[A](val user: Option[User], request: Request[A]) extends WrappedRequest[A](request)

object SessionAction extends ActionBuilder[SessionRequest] with ActionTransformer[Request, SessionRequest] {

  def transform[A](request: Request[A]): Future[SessionRequest[A]] = Future.successful {
    val optionalJsonRequest: Option[Request[AnyContent]] = request match {
      case r: Request[AnyContent] => Some(r)
      case _ => None
    }

    val result = {
      // Check if token is in JSON request
      for {
        jsonRequest <- optionalJsonRequest
        json <- jsonRequest.body.asJson
        sessionToken <- (json \ "auth" \ "session").asOpt[String]
        session <- SessionRepository.findByToken(sessionToken)
      } yield session
    } orElse {
      // Else check if the token is in a cookie
      for {
        cookie <- request.cookies.get("sessionid")
        sessionToken = cookie.value
        session <- SessionRepository.findByToken(sessionToken)
      } yield session
    } orElse {
      // Else check if its added in the header
      for {
        header <- request.headers.get("sessionid")
        session <- SessionRepository.findByToken(header)
      } yield session
    }

    result.map(x => new SessionRequest(x.user, request)).getOrElse(new SessionRequest(None, request))
  }
}

// Redirect the request if there is no user attached to the request
object WithUser extends ActionFilter[SessionRequest] {
  def filter[A](request: SessionRequest[A]): Future[Option[Result]] = Future.successful {
    request.user.map(x => None).getOrElse(Some(Redirect("http://website/loginpage")))
  }
}

然后您可以在动作中使用它

def index = (SessionAction andThen WithUser) { request =>
  val user = request.user
  Ok("Hello " + user.name)
}

我希望这能让您了解如何使用动作合成

答案 1 :(得分:1)

Stormpath的人员有一个示例Play应用程序,通过他们的后端服务提供身份验证。它的一些代码可能对您有用。

它使用用户名/密码而不是令牌,但修改它不应该很复杂。

他们已遵循此Play文档: https://www.playframework.com/documentation/2.0.8/ScalaSecurity

具体实现如下: https://github.com/stormpath/stormpath-play-sample/blob/dev/app/controllers/MainController.scala

Controller处理身份验证操作,并通过isAuthenticated Trait(依赖Secured)提供play.api.mvc.Security操作。此操作检查用户是否 如果他不是,则进行身份验证并将其重定向到登录屏幕:

/**
 * Action for authenticated users.
 */
def IsAuthenticated(f: => String => Request[AnyContent] => Future[SimpleResult]) =
  Security.Authenticated(email, onUnauthorized) { user =>
  Action.async { request =>
    email(request).map { login =>
      f(login)(request)
    }.getOrElse(Future.successful(onUnauthorized(request)))
  }
}

然后,需要经过身份验证的操作的每个控制器必须使用 安全特质:

object MyController extends Controller with Secured

这些操作被IsAuthenticated操作“包裹”:

def deleteItem(key: String) = IsAuthenticated { username => implicit request =>
  val future = Future {
    MyModel.deleteItem(request.session.get("id").get, key)
    Ok
  }
  future.map(
    status => status
  )
}

请注意,deleteItem操作不需要用户名,只需要key。但是,会话中会自动获取身份验证信息。因此,业务'API不会受到特定于安全性的参数的污染。

BTW,该应用程序似乎从未正式发布,因此请将此代码视为概念验证。