在单页应用中播放框架身份验证
Play Framework Authentication in a single page app
我正在尝试向我的 Play Framework 单页应用程序添加身份验证。
我想要的是:
def unsecured = Action {
Ok("This action is not secured")
}
def secured = AuthorizedAction {
// get the authenticated user's ID somehow
Ok("This action is secured")
}
对于传统的网络应用程序,我之前按照 Play Framework 文档这样做过:
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,如下所示:
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."
))
}
}
}
但是对于单页应用程序,这种方法不再有效。
James Ward 的这篇文章解释了如何设计新方法,并包括 Java 实现:
Securing SPA and rest services
该实现由 Marius Soutier 在 Scala 中重做:Securing SPA in Scala
在他的示例中,他实现了安全特性:
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"))
}
}
函数现在是这样保护的,而不是简单的操作:
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 定义为:
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" 函数变得如此复杂,并且更愿意使用 Action Builder(如第一个示例),在其中捕获并处理身份验证失败. (截至目前,如果我想保护 ping2 和 ping3 功能,每个功能都必须检查是否找到用户并处理 "not found" 案例)
我试图将一个动作生成器放在一起,灵感来自 Marius 的实现,尤其是他对 cacheApi 的使用,这是必要的。
但是AuthorizedAction是一个对象,需要注入cacheApi(所以需要把对象改成单例class),或者在没有定义的情况下不能在对象中声明。
我也觉得 AuthorizedAction 需要保留一个对象,以便用作:
def secured = AuthorizedAction {
任何人都可以消除混淆,并可能帮助解决一些实施细节问题吗?
非常感谢
我认为最简单的方法是使用 ActionBuilder
。您可以将动作生成器定义为 class(并向其传递一些依赖项)或对象。
首先,您需要定义一个包含用户信息的请求类型:
// 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)
}
}
}
现在您可以在您的控制器中使用它了:
val authorizedAction = new AuthorizedAction(userService)
def ping = authorizedAction { request =>
Ok(Json.obj("userId" -> request.user.id))
}
我正在尝试向我的 Play Framework 单页应用程序添加身份验证。
我想要的是:
def unsecured = Action {
Ok("This action is not secured")
}
def secured = AuthorizedAction {
// get the authenticated user's ID somehow
Ok("This action is secured")
}
对于传统的网络应用程序,我之前按照 Play Framework 文档这样做过:
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,如下所示:
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."
))
}
}
}
但是对于单页应用程序,这种方法不再有效。
James Ward 的这篇文章解释了如何设计新方法,并包括 Java 实现: Securing SPA and rest services
该实现由 Marius Soutier 在 Scala 中重做:Securing SPA in Scala
在他的示例中,他实现了安全特性:
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"))
}
}
函数现在是这样保护的,而不是简单的操作:
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 定义为:
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" 函数变得如此复杂,并且更愿意使用 Action Builder(如第一个示例),在其中捕获并处理身份验证失败. (截至目前,如果我想保护 ping2 和 ping3 功能,每个功能都必须检查是否找到用户并处理 "not found" 案例)
我试图将一个动作生成器放在一起,灵感来自 Marius 的实现,尤其是他对 cacheApi 的使用,这是必要的。
但是AuthorizedAction是一个对象,需要注入cacheApi(所以需要把对象改成单例class),或者在没有定义的情况下不能在对象中声明。
我也觉得 AuthorizedAction 需要保留一个对象,以便用作:
def secured = AuthorizedAction {
任何人都可以消除混淆,并可能帮助解决一些实施细节问题吗?
非常感谢
我认为最简单的方法是使用 ActionBuilder
。您可以将动作生成器定义为 class(并向其传递一些依赖项)或对象。
首先,您需要定义一个包含用户信息的请求类型:
// 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)
}
}
}
现在您可以在您的控制器中使用它了:
val authorizedAction = new AuthorizedAction(userService)
def ping = authorizedAction { request =>
Ok(Json.obj("userId" -> request.user.id))
}