2016-04-04 3 views
6

Kurz gesagt: Meine Anwendung verwendet die Play web framework Version 2.5.1. Ich möchte the Deadbolt authorization system und Slick verwenden, um auf die Benutzerautorisierungsinformationen in meiner Datenbank zuzugreifen. Wie kann ich das machen? Deadbolt ist speziell für Play gemacht und Spiele mit out-of-the-box integriert Slick kommt, so sollte es, wenn nicht sehr einfach möglich sein.So integrieren Sie Play (Web-Framework), Deadbolt (Autorisierung) und Slick (Datenbankzugriff)

Basierend auf "Integrating Deadbolt" aus der Deadbolt-Dokumentation, erweiterte ich die DeadboltHandler Eigenschaft. Seine abstrakte getSubject() Methode scheint wie der Ort, um die Datenbankabfrage zu tun (so sagt the documentation, aber ohne Beispiel). Diese Methode erhält als Argument eine AuthenticatedRequest und gibt die Subject, im Wesentlichen die Benutzer-ID, die authentifiziert wurde, zusammen mit Rollen und Berechtigungen (Berechtigungen) zurück.

Ich stecke fest, denn während Play mit Slick integration kommt, beschreibt die Dokumentation nur, wie man es in einem Play-Controller verwendet. (Hinweis Ich wünsche dies mit Dependency Injection zu tun, da mit Hilfe der globalen Lookups sind veraltet und fehleranfällige)

Ich verwende erfolgreich Deadbolt in meinem Controller Zugriff auf bestimmte Ressourcen zu beschränken, sondern die Steuerung scheint, wie der falsche Platz für Deadbolt Datenbankabfragen für Autorisierungsdetails zu tun (wenn es so wäre, wäre DeadboltHandler sinnlos). Der Controller Konstruktor Signaturdefinition sieht so etwas wie (Note der Controller greift auf die Standarddatenbank, die Web-Inhalte anstatt die Berechtigungsdatenbank speichert):

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

, das funktioniert. Jedoch in ähnlicher Weise die DeadboltHandler Erweiterung mit @Inject Anmerkungen versehen Slick Zugriff auf die Datenbank nicht bereitstellt:

class AuthHandler @Inject()(@play.db.NamedDatabase("auth") dbConfigProvider: DatabaseConfigProvider) 
    extends DeadboltHandler { 

wobei das Ergebnis

not enough arguments for constructor AuthHandler: (dbConfigProvider: play.api.db.slick.DatabaseConfigProvider)services.AuthHandler. 
Unspecified value parameter dbConfigProvider. 

Offensichtlich Spielen tut etwas Besonderes für Controller, so dass die @Inject Annotation funktioniert, etwas, was ich nicht verstehe. Ich nehme an, es liegt in der Natur der Konstruktion von Controllern mit einem Injektor anstelle des Schlüsselwortes new, aber meine Suche durch den Play Quellcode konnte mir nicht zeigen, was genau passiert. Wenn ich das finden könnte, könnte ich vielleicht diese Technik nachahmen, um eine DeadboltHandler zu konstruieren.

Ich sehe, dass das Spiel kommt mit Klassen wie GuiceInjector und GuiceInjectorBuilder, die klingen, als ob sie ein Teil der Lösung sein könnten, aber mein Experiment hat mir immer noch nicht gezeigt, wie, und wenn es Dokumentation zur Verwendung gibt sie im spezifischen Kontext einer DeadboldHandler Erweiterung, ich vermisse es.

Ich habe diese vorherige Frage gefunden: Scala (Play 2.4.x) How to call a class with @inject() annotation, die sehr auf den Punkt scheint. Leider ist es trotz eines halben Duzend Folgekommentars des ursprünglichen Posters noch unbeantwortet.Ich fühle mich, wenn ich die Antwort auf diese Frage hätte, würde ich die Antwort auf diese Frage hat, wenn meine Frage sehr spezifisch ist: Wie Wiedergabe verwendet und Deadbolt und Slick miteinander (in Scala).

Was mich am meisten verwirrt ist, dass dies scheint etwas, das genug sein sollte, dass es entweder in der Dokumentation erwähnt werden würde oder bereits auf SO gefragt worden. Wenn ich solche Hinweise nicht finde, bedeutet das normalerweise, dass ich etwas so Ungewöhnliches tue, dass niemand sonst jemals Gelegenheit hatte, darüber zu sprechen. Es scheint so, als ob es einfach genug sein sollte, dass ich optimistisch hoffe, dass ich etwas sehr Grundlegendes verpasse, und ich freue mich auf eine freundliche Seele, die mich über dieses Wissen informiert.

+1

ich später eine vollständige Antwort aufschreiben werde, aber jetzt können Sie einen Blick auf https nehmen: // GitHub. com/schaloner/deadbolt-auth0-scala/blob/master/app/sicherheit/MyDeadboltHandler.scala # L37 und https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport. scala # L56 - In diesem Beispiel wird eine externe Identitätsverwaltungsplattform anstelle einer Datenbank verwendet. Sie sollten jedoch https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport neu schreiben können .scala # L105 an d verwende den größten Teil des Codes wie er ist. –

+0

Du löst mich, segne dich Steve! Nehmen Sie so lange, wie Sie eine vollständige Antwort schreiben müssen; Sie haben ein korrektes Antwortzeichen, das auf Sie wartet. –

Antwort

2

Wie Sie in Ihrer Frage notiert haben, ist der Ort zum Abrufen des Benutzers in DeadboltHandler.getSubject. Sie können den datenbankspezifischen Code tatsächlich in eine eigene Klasse verschieben, also habe ich das in diesem Beispiel getan.

Dies ist eine generische Implementierung von DeadboltHandler; Sie sollten in der Lage sein, es in Ihren Code einzufügen und so zu verwenden, wie es ist, da die Persistenzspezifikationen später behandelt werden.

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)) 
    } 
} 

Die Notwendigkeit, die Datenbank gehen nun auf die Fälle reduziert, wenn der Gegenstand in die Anfrage nicht bereits platziert wurde. Beachten Sie den Kommentar zum Ersetzen von request.session.get("userId") mit dem Sie jedoch den Benutzer identifizieren.

Der Zugriff auf die Persistenz des Patienten wird von der Klasse AuthSupport übernommen. Dies isoliert den DB-Zugriff vom DeadboltHandler. Es ist ziemlich einfach, hauptsächlich, weil Sie dies mit Ihrer Slick-Abfrage füllen.

@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] = ??? 
} 

Um dies zu belichten, dann müssen Sie ein Modul erstellen und sie in Ihrem application.conf registrieren.

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 
     ) 
} 

es in application.conf Deklarieren ist die übliche Sache play.modules.enabled der Verwendung:

play { 
    modules { 
    enabled += be.objectify.deadbolt.scala.DeadboltModule 
    enabled += modules.CustomBindings 
    } 
} 
+0

Ausgezeichnet, nochmals vielen Dank! Eine Frage: In 'MyDeadboltHandler' injizierst du eine Instanz von' CacheApi', die aber anscheinend nicht benutzt wird. Eventuell wird auch kein Cache mehr in 'AuthSupport.getUser()' erstellt. Was ist der Zweck des Einfügens von 'Cache' in' MyDeadboltHandler'? –

+1

Ich habe das Beispiel aus dem Link, den ich in einem Kommentar gepostet habe, in Ihre Frage umgeschrieben und das Caching entfernt, um es einfach zu halten. Ich habe vergessen, die Cache-Injektion zu entfernen - ich habe es bearbeitet, um dies zu korrigieren. –