2016-04-04 5 views
6

Вкратце: мое приложение использует версию 2.5.1.. Я хочу использовать the Deadbolt authorization system и Slick для доступа к информации о авторизации пользователя в моей базе данных. Как я могу это сделать? Deadbolt сделан специально для Play и Play поставляется с Slick интегрированной вне коробки, так что это должно быть возможным, если не очень легко.Как интегрировать Play (веб-фреймворк), Deadbolt (авторизация) и Slick (доступ к базе данных)

На основании "Integrating Deadbolt" из документации Deadbolt я расширил признак DeadboltHandler. Его абстрактный метод getSubject() выглядит как место для запроса базы данных (так говорит the documentation, но без какого-либо примера). Этот метод принимает в качестве аргумента AuthenticatedRequest и возвращает Subject, в основном идентификатор пользователя, который был аутентифицирован, а также роли и разрешения (авторизации).

Я застрял, потому что в то время как Play поставляется с Slick integration, документация описывает только, как использовать его с контроллера Play. (Примечания Я хотел сделать эту инъекцию с использованием зависимостей, так как с помощью глобального Lookups является устаревшим и подверженными ошибки)

я успешно использую ригель в моем контроллере, чтобы ограничить доступ к определенным ресурсам, но контроллер кажется, неправильное место для Deadbolt, чтобы делать запросы к базе данных для деталей авторизации (если бы это было, то DeadboltHandler было бы бесцельным). Определение подписи конструктора контроллера выглядит примерно так (примечания контроллер обращается к базе данных по умолчанию, который хранит веб-контент, а не базы данных авторизации):

class Application @Inject()(
    dbConfigProvider: DatabaseConfigProvider, 
    playConfig: play.api.Configuration, 
    deadbolt: DeadboltActions 
) extends Controller { 

Это работает. Однако, как и аннотирования расширение DeadboltHandler с @Inject не в состоянии обеспечить 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, но мой поиск по исходному коду не показал мне, что именно происходит. Если бы я мог найти это, возможно, я мог бы подражать этой технике, чтобы построить DeadboltHandler.

Я вижу, что в игру входят такие классы, как GuiceInjector и GuiceInjectorBuilder, которые звучат так, как будто они могут быть частью решения, но мои эксперименты пока не показали мне, как и если есть какая-либо документация о том, как использовать их в конкретном контексте расширения DeadboldHandler, я его не хватает.

Я нашел этот предыдущий вопрос: Scala (Play 2.4.x) How to call a class with @inject() annotation, который кажется очень сверху пункт. К сожалению, несмотря на полдюжины последующих комментариев с оригинального плаката, он еще не принят.Я чувствую, если у меня был ответ на этот вопрос, я бы ответ на этот вопрос, хотя мой вопрос очень конкретно: как использовать Play и ригель и Slick друг с другом (в Scala).

Что больше всего меня озадачивает, так это то, что это похоже на то, что должно быть достаточно общим, чтобы оно было либо упомянуто в документации, либо было спрошено уже о SO. Моя неспособность найти такие ссылки обычно означает, что я делаю что-то настолько уникальное, что никто никогда не имел возможности говорить об этом. Конечно, кажется, что это должно быть достаточно просто, что я с оптимизмом надеюсь, что у меня пропало что-то очень основное, и я с нетерпением жду какой-то доброй души, информирующей меня об этом знании.

+1

Я напишу полный ответ позже, но сейчас вы посмотрите на HTTPS: // может GitHub. com/schaloner/deadbolt-auth0-scala/blob/master/app/security/MyDeadboltHandler.scala # L37 и https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport. scala # L56 - в этом примере вместо базы данных используется внешняя платформа управления идентификационными данными, но вы должны переписать https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport .scala # L105 и использовать большая часть кода как есть. –

+0

Вы раскалываете меня, благословите вас, Стива! Возьмите столько, сколько вам нужно, чтобы написать полный ответ; у вас есть знак правильного ответа, ожидающий вас. –

ответ

2

Как вы отметили в своем вопросе, место для извлечения пользователя находится в 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. Это изолирует доступ БД от 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 
    } 
} 
+0

Отлично, спасибо еще раз! Один вопрос: в 'MyDeadboltHandler' вы вводите экземпляр' CacheApi', но он, по-видимому, не используется. Кроме того, может ли кэширование более корректно выполняться в 'AuthSupport.getUser()'? Какова цель ввода 'cache' в' MyDeadboltHandler'? –

+1

Я переписал пример из ссылки, которую я опубликовал в комментарии к вашему вопросу, и удалил кеширование, чтобы оно было простым. Я забыл удалить кеш-инъекцию - я отредактировал, чтобы исправить это. –

 Смежные вопросы

  • Нет связанных вопросов^_^