如何集成Play(web框架)、Deadbolt(授权)和Slick(数据库访问)



简单地说:我的应用程序使用Play web框架2.5.1版本。我想使用Deadbolt授权系统和Slick来访问数据库中的用户授权信息。我该怎么做Deadbolt是专门为Play制作的,而Play附带了开箱即用的Slick,所以这应该是可能的,如果不是很容易的话。

基于Deadbolt文档中的"集成Deadbolt",我扩展了DeadboltHandler特性。它的抽象getSubject()方法似乎是进行数据库查询的地方(文档中这样说,但没有任何示例)。该方法接收AuthenticatedRequest作为参数,并返回Subject,基本上是经过身份验证的用户id,以及角色和权限(授权)。

我被卡住了,因为虽然Play带有Slick集成,但文档只描述了如何在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

我看到这出戏附带了GuiceInjector和GuiceInject Builder等类,听起来它们可能是解决方案的一部分,但我的实验还没有向我展示如何使用它们,如果有任何关于如何在DeadboldHandler扩展的特定上下文中使用它们的文档,我就错过了。

我发现了之前的问题:Scala(Play2.4.x)如何用@inject()注释来调用一个类,这似乎非常中肯。不幸的是,尽管最初的海报上有六条后续评论,但仍未得到回复。我觉得如果我有这个问题的答案,我就会有这个问题,尽管我的问题很具体:如何使用PlayDeadbolt以及Slick(在Scala中)。

最让我困惑的是,这似乎是一件应该足够常见的事情,它要么会在文档中被提及,要么已经在SO上被问及。我找不到任何这样的参考文献通常意味着我正在做一件独特的错误,以至于其他人都没有机会谈论它我乐观地希望我错过了一些非常基本的东西,我期待着某个善良的灵魂告诉我这些知识。

正如您在问题中所指出的,检索用户的位置在DeadboltHandler.getSubject中。实际上,您可以将特定于数据库的代码移动到它自己的类中,所以在本例中,我就是这么做的。

这是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类提供对主题持久性的访问。这将DB访问与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
  }
}

最新更新