如何整合Play(网络框架)、Deadbolt(授权)和Slick(数据库访问)
How to integrate Play (web framework), Deadbolt (authorization) and Slick (database access)
简要说明:我的应用程序使用 Play web framework version 2.5.1. I want to use the Deadbolt authorization system, and Slick 访问我数据库中的用户授权信息。我怎样才能做到这一点? Deadbolt 是专门为 Play 制作的,Play 附带 Slick 集成开箱即用,所以即使不是很容易也应该是可能的。
基于 "Integrating Deadbolt" from the Deadbolt documentation, I extended the DeadboltHandler
trait. Its abstract getSubject()
method seems like the place to do the database query (so says the documentation 但没有任何示例)。该方法接收 AuthenticatedRequest
和 returns Subject
作为参数,基本上是经过身份验证的用户 ID,以及角色和权限(授权)。
我被卡住了,因为虽然 Play 附带 Slick integration,但文档仅描述了如何在 Play 控制器中使用它。 (请注意,我想使用依赖项注入来执行此操作,因为不推荐使用全局查找并且容易出错)
我成功地在我的控制器中使用 Deadbolt 来限制对某些资源的访问,但是控制器似乎放错了 Deadbolt对授权详细信息进行数据库查询(如果是,那么 DeadboltHandler
将毫无意义)。控制器构造函数签名定义类似于(注意控制器访问存储 Web 内容的默认数据库而不是授权数据库):
class Application @Inject()(
dbConfigProvider: DatabaseConfigProvider,
playConfig: play.api.Configuration,
deadbolt: DeadboltActions
) extends Controller {
行得通。但是,类似地用 @Inject
注释 DeadboltHandler
扩展无法提供 Slick 对数据库的访问:
class AuthHandler @Inject()(@play.db.NamedDatabase("auth") dbConfigProvider: DatabaseConfigProvider)
extends DeadboltHandler {
结果是
not enough arguments for constructor AuthHandler: (dbConfigProvider: play.api.db.slick.DatabaseConfigProvider)services.AuthHandler.
Unspecified value parameter dbConfigProvider.
显然,Play 为控制器做了一些特殊的事情,以便 @Inject
注释起作用,我不了解这一点。我认为这是使用注入器而不是 new
关键字构建控制器的本质,但是我通过 Play 源代码的搜索未能告诉我到底发生了什么.如果我能找到那个,也许我可以模仿那个技术来构造一个 DeadboltHandler
.
我看到 play 带有 类,例如 GuiceInjector and GuiceInjectorBuilder,听起来好像它们可能是解决方案的一部分,但我的实验还没有告诉我如何,如果有是否有关于如何在 DeadboldHandler
扩展的特定上下文中使用它们的任何文档,我错过了它。
我发现了上一个问题:Scala (Play 2.4.x) How to call a class with @inject() annotation,这看起来非常切题。不幸的是,尽管原始发帖人发表了六条后续评论,但仍未得到答复。我觉得如果我有那个问题的答案我就会有这个问题的答案,虽然我的问题非常具体:如何使用 Play 和 Deadbolt 和 Slick 彼此(在 Scala 中)。
最让我困惑的是,这似乎应该很常见,以至于它要么在文档中提到,要么已经在 SO 上被问到。我找不到任何此类参考资料通常意味着我在做一些非常错误的事情,以至于没有其他人有机会谈论它。它看起来似乎应该足够简单,我乐观地希望我错过了一些非常基本的东西,我期待着一些好心的人告诉我这些知识。
如您在问题中所述,检索用户的位置在 DeadboltHandler.getSubject
。实际上,您可以将特定于数据库的代码移动到它自己的 class 中,所以在这个示例中,我就是这样做的。
这是DeadboltHandler
的通用实现;您应该能够将它放入您的代码中并按原样使用它,因为稍后将处理持久性细节。
import javax.inject.{Inject, Singleton}
import be.objectify.deadbolt.scala.models.Subject
import be.objectify.deadbolt.scala.{AuthenticatedRequest, DeadboltHandler, DynamicResourceHandler}
import models.{LogInForm, User}
import play.api.mvc.{Request, Result, Results}
import play.twirl.api.HtmlFormat
import views.html.security.denied
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@Singleton
class MyDeadboltHandler @Inject() (authSupport: AuthSupport) extends DeadboltHandler {
override def beforeAuthCheck[A](request: Request[A]): Future[Option[Result]] = Future {None}
override def getDynamicResourceHandler[A](request: Request[A]): Future[Option[DynamicResourceHandler]] = Future {None}
/**
* Get the current user.
*
* @param request the HTTP request
* @return a future for an option maybe containing the subject
*/
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
Future {
request.subject.orElse {
// replace request.session.get("userId") with how you identify the user
request.session.get("userId") match {
case Some(userId) => authSupport.getUser(userId)
case _ => None
}
}}
/**
* Handle instances of authorization failure.
*
* @param request the HTTP request
* @return either a 401 or 403 response, depending on the situation
*/
override def onAuthFailure[A](request: AuthenticatedRequest[A]): Future[Result] = {
def toContent(maybeSubject: Option[Subject]): (Boolean, HtmlFormat.Appendable) =
maybeSubject.map(subject => subject.asInstanceOf[User])
.map(user => (true, denied(Some(user))))
.getOrElse {(false, views.html.security.logIn(LogInForm.logInForm))}
getSubject(request).map(maybeSubject => toContent(maybeSubject))
.map(subjectPresentAndContent =>
if (subjectPresentAndContent._1) Results.Forbidden(subjectPresentAndContent._2)
else Results.Unauthorized(subjectPresentAndContent._2))
}
}
访问数据库的需要现在减少到主题尚未放入请求中的情况。请注意有关将 request.session.get("userId")
替换为您标识用户的注释。
然后 AuthSupport
class 提供对主题持久性的访问。这将数据库访问与 DeadboltHandler 隔离开来。它非常简单,主要是因为您将使用 Slick 查询来填充它。
@Singleton
class AuthSupport @Inject()(dbConfigProvider: DatabaseConfigProvider) {
// set up your usual Slick support
// use Slick to get the subject from the database
def getUser(userId: String): Option[User] = ???
}
要公开它,您需要创建一个模块并将其注册到您的 application.conf
。
import be.objectify.deadbolt.scala.DeadboltHandler
import be.objectify.deadbolt.scala.cache.HandlerCache
import security.{AuthSupport, MyDeadboltHandler, MyHandlerCache}
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
class CustomBindings extends Module {
override def bindings(environment: Environment,
configuration: Configuration): Seq[Binding[_]] =
Seq(
bind[DeadboltHandler].to[MyDeadboltHandler],
bind[AuthSupport].toSelf,
// other bindings, such as HandlerCache
)
}
在application.conf
中声明是使用play.modules.enabled
的常见问题:
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
enabled += modules.CustomBindings
}
}
简要说明:我的应用程序使用 Play web framework version 2.5.1. I want to use the Deadbolt authorization system, and Slick 访问我数据库中的用户授权信息。我怎样才能做到这一点? Deadbolt 是专门为 Play 制作的,Play 附带 Slick 集成开箱即用,所以即使不是很容易也应该是可能的。
基于 "Integrating Deadbolt" from the Deadbolt documentation, I extended the DeadboltHandler
trait. Its abstract getSubject()
method seems like the place to do the database query (so says the documentation 但没有任何示例)。该方法接收 AuthenticatedRequest
和 returns Subject
作为参数,基本上是经过身份验证的用户 ID,以及角色和权限(授权)。
我被卡住了,因为虽然 Play 附带 Slick integration,但文档仅描述了如何在 Play 控制器中使用它。 (请注意,我想使用依赖项注入来执行此操作,因为不推荐使用全局查找并且容易出错)
我成功地在我的控制器中使用 Deadbolt 来限制对某些资源的访问,但是控制器似乎放错了 Deadbolt对授权详细信息进行数据库查询(如果是,那么 DeadboltHandler
将毫无意义)。控制器构造函数签名定义类似于(注意控制器访问存储 Web 内容的默认数据库而不是授权数据库):
class Application @Inject()(
dbConfigProvider: DatabaseConfigProvider,
playConfig: play.api.Configuration,
deadbolt: DeadboltActions
) extends Controller {
行得通。但是,类似地用 @Inject
注释 DeadboltHandler
扩展无法提供 Slick 对数据库的访问:
class AuthHandler @Inject()(@play.db.NamedDatabase("auth") dbConfigProvider: DatabaseConfigProvider)
extends DeadboltHandler {
结果是
not enough arguments for constructor AuthHandler: (dbConfigProvider: play.api.db.slick.DatabaseConfigProvider)services.AuthHandler.
Unspecified value parameter dbConfigProvider.
显然,Play 为控制器做了一些特殊的事情,以便 @Inject
注释起作用,我不了解这一点。我认为这是使用注入器而不是 new
关键字构建控制器的本质,但是我通过 Play 源代码的搜索未能告诉我到底发生了什么.如果我能找到那个,也许我可以模仿那个技术来构造一个 DeadboltHandler
.
我看到 play 带有 类,例如 GuiceInjector and GuiceInjectorBuilder,听起来好像它们可能是解决方案的一部分,但我的实验还没有告诉我如何,如果有是否有关于如何在 DeadboldHandler
扩展的特定上下文中使用它们的任何文档,我错过了它。
我发现了上一个问题:Scala (Play 2.4.x) How to call a class with @inject() annotation,这看起来非常切题。不幸的是,尽管原始发帖人发表了六条后续评论,但仍未得到答复。我觉得如果我有那个问题的答案我就会有这个问题的答案,虽然我的问题非常具体:如何使用 Play 和 Deadbolt 和 Slick 彼此(在 Scala 中)。
最让我困惑的是,这似乎应该很常见,以至于它要么在文档中提到,要么已经在 SO 上被问到。我找不到任何此类参考资料通常意味着我在做一些非常错误的事情,以至于没有其他人有机会谈论它。它看起来似乎应该足够简单,我乐观地希望我错过了一些非常基本的东西,我期待着一些好心的人告诉我这些知识。
如您在问题中所述,检索用户的位置在 DeadboltHandler.getSubject
。实际上,您可以将特定于数据库的代码移动到它自己的 class 中,所以在这个示例中,我就是这样做的。
这是DeadboltHandler
的通用实现;您应该能够将它放入您的代码中并按原样使用它,因为稍后将处理持久性细节。
import javax.inject.{Inject, Singleton}
import be.objectify.deadbolt.scala.models.Subject
import be.objectify.deadbolt.scala.{AuthenticatedRequest, DeadboltHandler, DynamicResourceHandler}
import models.{LogInForm, User}
import play.api.mvc.{Request, Result, Results}
import play.twirl.api.HtmlFormat
import views.html.security.denied
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@Singleton
class MyDeadboltHandler @Inject() (authSupport: AuthSupport) extends DeadboltHandler {
override def beforeAuthCheck[A](request: Request[A]): Future[Option[Result]] = Future {None}
override def getDynamicResourceHandler[A](request: Request[A]): Future[Option[DynamicResourceHandler]] = Future {None}
/**
* Get the current user.
*
* @param request the HTTP request
* @return a future for an option maybe containing the subject
*/
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
Future {
request.subject.orElse {
// replace request.session.get("userId") with how you identify the user
request.session.get("userId") match {
case Some(userId) => authSupport.getUser(userId)
case _ => None
}
}}
/**
* Handle instances of authorization failure.
*
* @param request the HTTP request
* @return either a 401 or 403 response, depending on the situation
*/
override def onAuthFailure[A](request: AuthenticatedRequest[A]): Future[Result] = {
def toContent(maybeSubject: Option[Subject]): (Boolean, HtmlFormat.Appendable) =
maybeSubject.map(subject => subject.asInstanceOf[User])
.map(user => (true, denied(Some(user))))
.getOrElse {(false, views.html.security.logIn(LogInForm.logInForm))}
getSubject(request).map(maybeSubject => toContent(maybeSubject))
.map(subjectPresentAndContent =>
if (subjectPresentAndContent._1) Results.Forbidden(subjectPresentAndContent._2)
else Results.Unauthorized(subjectPresentAndContent._2))
}
}
访问数据库的需要现在减少到主题尚未放入请求中的情况。请注意有关将 request.session.get("userId")
替换为您标识用户的注释。
然后 AuthSupport
class 提供对主题持久性的访问。这将数据库访问与 DeadboltHandler 隔离开来。它非常简单,主要是因为您将使用 Slick 查询来填充它。
@Singleton
class AuthSupport @Inject()(dbConfigProvider: DatabaseConfigProvider) {
// set up your usual Slick support
// use Slick to get the subject from the database
def getUser(userId: String): Option[User] = ???
}
要公开它,您需要创建一个模块并将其注册到您的 application.conf
。
import be.objectify.deadbolt.scala.DeadboltHandler
import be.objectify.deadbolt.scala.cache.HandlerCache
import security.{AuthSupport, MyDeadboltHandler, MyHandlerCache}
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
class CustomBindings extends Module {
override def bindings(environment: Environment,
configuration: Configuration): Seq[Binding[_]] =
Seq(
bind[DeadboltHandler].to[MyDeadboltHandler],
bind[AuthSupport].toSelf,
// other bindings, such as HandlerCache
)
}
在application.conf
中声明是使用play.modules.enabled
的常见问题:
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
enabled += modules.CustomBindings
}
}