【问题标题】:How to integrate Play (web framework), Deadbolt (authorization) and Slick (database access)如何集成 Play(Web 框架)、Deadbolt(授权)和 Slick(数据库访问)
【发布时间】:2016-07-24 09:00:08
【问题描述】:

简而言之:我的应用程序使用 Play web framework 版本 2.5.1。我想使用the Deadbolt authorization systemSlick 来访问我数据库中的用户授权信息。我怎样才能做到这一点? Deadbolt 是专门为 Play 制作的,而 Play 带有开箱即用的 Slick 集成,因此如果不是很容易,这应该是可能的。

基于 Deadbolt 文档中的 "Integrating Deadbolt",我扩展了 DeadboltHandler 特征。它的抽象getSubject() 方法似乎是进行数据库查询的地方(所以说the documentation,但没有任何示例)。该方法接收AuthenticatedRequest 作为参数并返回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 带有诸如 GuiceInjectorGuiceInjectorBuilder 之类的类,听起来好像它们可能是解决方案的一部分,但我的实验还没有告诉我如何,以及是否有任何关于如何在 DeadboldHandler 扩展的特定上下文中使用它们,我想念它。

我发现了这个先前的问题:Scala (Play 2.4.x) How to call a class with @inject() annotation,这似乎非常正确。不幸的是,尽管有六个来自原始海报的后续 cmets,但仍未得到答复。我觉得如果我有这个问题的答案,我就会有这个问题的答案,尽管我的问题非常具体:如何使用 PlayDeadbolt彼此光滑(在 Scala 中)。

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

【问题讨论】:

标签: scala playframework slick playframework-2.5 deadbolt-2


【解决方案1】:

正如您在问题中所述,检索用户的位置位于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
  }
}

【讨论】:

  • 太好了,再次感谢!一个问题:在MyDeadboltHandler 中注入了CacheApi 的一个实例,但它显然没有被使用。此外,在AuthSupport.getUser() 中进行缓存是否更合适?将cache注入MyDeadboltHandler的目的是什么?
  • 我重写了我在对您的问题的评论中发布的链接中的示例,并删除了缓存以保持简单。我忘了删除缓存注入 - 我已编辑以更正此问题。
猜你喜欢
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-02
  • 2012-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多