在单页应用程序中播放框架身份验证 [英] Play Framework Authentication in a single page app

查看:88
本文介绍了在单页应用程序中播放框架身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试向我的Play Framework单页应用添加身份验证.

I am trying to add authentication to my Play Framework single page app.

我想要的东西是这样的:

What I would like to have is something like:

def unsecured = Action {
    Ok("This action is not secured")
}

def secured = AuthorizedAction {
    // get the authenticated user's ID somehow
    Ok("This action is secured")
}

对于传统的Web应用程序,我之前是按照Play Framework文档进行的:

For a traditional web app, I had previously done this, following Play Framework docs:

def authenticate = Action { implicit request =>
  loginForm.bindFromRequest.fold(
    formWithErrors => BadRequest(views.html.login(formWithErrors)),
    user => {
      Redirect(routes.Application.home).withSession(Security.username -> user._1)
    }
  )
}

def logout = Action {
  Redirect(routes.Auth.login).withNewSession.flashing(
    "success" -> "You are now logged out."
  )
}

,授权操作"将ActionBuilder扩展如下:

and the Authorized Action is extending ActionBuilder as follows:

object AuthorizedAction extends ActionBuilder[Request] with Results {

  /**
   * on auth success: proceed with the request
   * on auth failure: redirect to login page with flash
   */
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    // TODO: is "isDefined" enough to determine that user is logged in?
    if(request.session.get("username").isDefined) {
      block(request)
    }
    else {
      Future.successful(Redirect(routes.Auth.login).flashing(
        "failure" -> "You must be logged in to access this page."
      ))
    }
  }
}

但是对于单页应用程序,此方法不再完全有效.

For single page applications however, this approach doesn't exactly work anymore.

James Ward的本文介绍了如何设计新方法,并包括Java实现: 确保SPA和休息服务的安全

This article by James Ward explains how the new approach is to be designed, and includes a Java implementation: Securing SPA and rest services

Marius Soutier在Scala中重做了该实现:保护Scala中的SPA

The implementation was redone in Scala by Marius Soutier: Securing SPA in Scala

在他的示例中,他实现了安全性特征:

In his example, he implements a Security trait:

trait Security { self: Controller =>

  val cache: CacheApi

  val AuthTokenHeader = "X-XSRF-TOKEN"
  val AuthTokenCookieKey = "XSRF-TOKEN"
  val AuthTokenUrlKey = "auth"

  /** Checks that a token is either in the header or in the query string */
  def HasToken[A](p: BodyParser[A] = parse.anyContent)(f: String => Long => Request[A] => Result): Action[A] =
    Action(p) { implicit request =>
      val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey))
      maybeToken flatMap { token =>
        cache.get[Long](token) map { userid =>
          f(token)(userid)(request)
        }
      } getOrElse Unauthorized(Json.obj("err" -> "No Token"))
    }

}

现在可以像这样保护功能,而不是简单的操作:

Functions are now secured like this instead of a plain Action:

def ping() = HasToken() { token => userId => implicit request =>
  user.findByID (userId) map { user =>
    Ok(Json.obj("userId" -> userId)).withToken(token -> userId)
  } getOrElse NotFound (Json.obj("err" -> "User Not Found"))
}

其中.withToken定义为:

where .withToken is defined as:

implicit class ResultWithToken(result: Result) {
  def withToken(token: (String, Long)): Result = {
    cache.set(token._1, token._2, CacheExpiration)
    result.withCookies(Cookie(AuthTokenCookieKey, token._1, None, httpOnly = false))
  }

  def discardingToken(token: String): Result = {
    cache.remove(token)
    result.discardingCookies(DiscardingCookie(name = AuthTokenCookieKey))
  }
}

我不喜欢上面的"ping"功能变得多么复杂,并且宁愿使用动作生成器(如第一个示例),在其中仅捕获并处理身份验证失败. (到目前为止,如果我要保护ping2和ping3函数,则每个函数都必须检查是否找到了用户并处理未找到"的情况)

I am not liking how complex the "ping" function above has become, and would have preferred to use an Action Builder (like the first example), where auth failure is caught and dealt with at a single point. (as of now, if I want to secure functions ping2 and ping3, each one has to check whether the user is found and deal with the "not found" case)

在Marius的实现(尤其是他对必要的cacheApi的使用)的启发下,我试图将动作构建器组合在一起.

I have tried to put together an action builder, inspired by Marius' implementation, most particularly his use of the cacheApi which is necessary.

但是AuthorizedAction是一个对象,并且需要注入cacheApi(因此需要将该对象更改为singleton类),或者无法在未定义的对象中声明它.

However the AuthorizedAction is an object, and cacheApi needs to be injected (so need to change the object to singleton class), or cannot be declared in an object without being defined.

我还觉得AuthorizedAction需要保留为一个对象,以便用作:

I also feel like the AuthorizedAction needs to remain an object, in order to be used as:

def secured = AuthorizedAction {

任何人都可以消除混乱,并可能对一些实施细节有所帮助吗?

Would anyone please clear up the confusion, and possibly help with some implementation details?

非常感谢

推荐答案

我认为最简单的方法是使用ActionBuilder.您可以将动作生成器定义为类(并向其传递一些依赖项)或对象.

The simplest way in my opinion is to go with ActionBuilder. You can define an action builder as a class (and pass it some dependencies) or as an object.

首先,您需要定义一个类型的请求,该请求将包含有关用户的信息:

First you'll need to define a type a request that will contain the information about the user:

// You can add other useful information here
case class AuthorizedRequest[A](request: Request[A], user: User) extends WrappedRequest(request)

现在定义您的ActionBuilder

class AuthorizedAction(userService: UserService) extends ActionBuilder[AuthorizedRequest] {

  override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = {
    request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey)) match {
      case Some(token) => userService.findByToken(token).map {
        case Some(user) =>
          val req = AuthorizedRequest(request, user)
          block(req)
        case None => Future.successful(Results.Unauthorized)
      }
      case None => Future.successful(Results.Unauthorized)
    }

  }
}

现在您可以在控制器中使用它了

Now you can use it in your controller:

val authorizedAction = new AuthorizedAction(userService)
def ping = authorizedAction { request =>
  Ok(Json.obj("userId" -> request.user.id))
}

这篇关于在单页应用程序中播放框架身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆