2016-04-04 11 views
6

In breve: la mia applicazione utilizza la versione 2.5.1 Play web framework. Voglio utilizzare the Deadbolt authorization system e Slick per accedere alle informazioni di autorizzazione dell'utente nel mio database. Come posso fare questo? Catenaccio è fatto appositamente per Gioca, e Gioca viene fornito con Slick integrato out-of-the-box, per cui dovrebbe essere possibile se non molto facile.Come integrare Play (framework web), Deadbolt (autorizzazione) e Slick (accesso al database)

In base a "Integrating Deadbolt" dalla documentazione di Deadbolt, ho esteso il tratto DeadboltHandler. Il suo metodo astratto getSubject() sembra il posto dove eseguire la query del database (così dice the documentation ma senza alcun esempio). Tale metodo riceve come argomento un AuthenticatedRequest e restituisce lo Subject, in pratica l'id utente che è stato autenticato, insieme a ruoli e permessi (autorizzazioni).

Sono bloccato, perché mentre Play viene fornito con Slick integration, la documentazione descrive solo come utilizzarlo da un controller di riproduzione. (nota Sono voler fare questo l'iniezione di dipendenza utilizzando in quanto utilizza le ricerche a livello mondiale è deprecato e soggetto a errori)

adesso utilizzo Catenaccio nel mio controller per limitare l'accesso a determinate risorse, ma il controller sembra il posto sbagliato per deadbolt per eseguire query di database per i dettagli di autorizzazione (se lo fosse, quindi lo DeadboltHandler non avrebbe voluto). La definizione firma del costruttore del controller sembra qualcosa di simile (nota il controller accede al database predefinito che memorizza i contenuti web, piuttosto che il database delle autorizzazioni):

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

che funziona. Tuttavia, allo stesso modo annotare l'estensione DeadboltHandler con @Inject non riesce a fornire Slick accesso al database:

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

il risultato è

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

Ovviamente, Gioca fa qualcosa di speciale per i controller in modo che il @Inject funziona l'annotazione, qualcosa di cui mi manca la comprensione. Presumo che sia nella natura di costruire controllori usando un iniettore piuttosto che la parola chiave new, ma la mia ricerca attraverso il codice sorgente non è riuscita a mostrarmi cosa sta succedendo esattamente. Se riuscissi a trovarlo, forse potrei imitare quella tecnica per costruire un DeadboltHandler.

vedo che il gioco viene fornito con classi come GuiceInjector e GuiceInjectorBuilder, che suonano come se fossero parte della soluzione, ma la mia sperimentazione non è ancora riuscito a farmi vedere come, e se non v'è alcuna documentazione su come utilizzare loro nel contesto specifico di un'estensione DeadboldHandler, mi manca.

Ho trovato questa domanda precedente: Scala (Play 2.4.x) How to call a class with @inject() annotation, che sembra molto sul punto. Sfortunatamente, nonostante una mezza dozzina di commenti di follow-up dal poster originale, non ha ancora ricevuto risposta.Mi sento se avessi la risposta a questa domanda che vorrei avere la risposta a questa domanda, anche se la mia domanda è molto specifica: come utilizzare gioco e Catenaccio e Slick uno con l'altro (in Scala).

Quello che mi confonde di più è che questo sembra qualcosa che dovrebbe essere abbastanza comune da essere menzionato nella documentazione o che è stato già chiesto riguardo a SO. Il fatto che non riesca a trovare tali riferimenti in genere significa che sto facendo qualcosa di talmente unico che nessun altro ha mai avuto occasione di parlarne. Certamente sembra che dovrebbe essere abbastanza semplice da sperare ottimisticamente che mi manchi qualcosa di molto semplice, e attendo con impazienza un'anima gentile che mi informi di questa conoscenza.

+1

scriverò una risposta completa dopo, ma per ora si può dare un'occhiata a https: // GitHub. com/schaloner/deadbolt-auth0-scala/blob/master/app/security/MyDeadboltHandler.scala # L37 e https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport. scala # L56 - questo esempio utilizza una piattaforma di gestione delle identità esterna al posto di un database, ma dovresti essere in grado di riscrivere https://github.com/schaloner/deadbolt-auth0-scala/blob/master/app/security/AuthSupport .scala # L105 an d usare la maggior parte del codice così com'è. –

+0

Mi hai lasciato, benedici Steve! Prendi tutto il tempo necessario per scrivere una risposta completa; hai un segno di risposta corretta che ti aspetta. –

risposta

2

Come indicato nella domanda, il luogo in cui recuperare l'utente è DeadboltHandler.getSubject. È possibile spostare il codice specifico del database nella propria classe, quindi in questo esempio è ciò che ho fatto.

Questa è un'implementazione generica di DeadboltHandler; dovresti essere in grado di inserirlo nel tuo codice e usarlo praticamente così com'è, dato che le specifiche di persistenza saranno trattate in seguito.

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

La necessità di andare al database è ora ridotta ai casi in cui il soggetto non è già stato inserito nella richiesta. Si noti il ​​commento sulla sostituzione di request.session.get("userId") con l'identificazione dell'utente.

L'accesso alla persistenza del soggetto viene quindi fornito dalla classe AuthSupport. Questo isola l'accesso ai DB da DeadboltHandler. È piuttosto semplice, principalmente perché lo riempirai con la tua query 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] = ??? 
} 

Per esporre questo, è necessario creare un modulo e registrarlo nel vostro 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 
     ) 
} 

Dichiarare in application.conf è la solita questione di usare play.modules.enabled:

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

Eccellente, grazie ancora! Una domanda: in 'MyDeadboltHandler' si inietta un'istanza di' CacheApi', ma apparentemente non viene utilizzata. Inoltre, il caching potrebbe non essere fatto in modo appropriato in 'AuthSupport.getUser()'? Qual è lo scopo di iniettare 'cache' in' MyDeadboltHandler'? –

+1

Ho riscritto l'esempio dal link che ho postato in un commento alla tua domanda e rimosso la cache per mantenerlo semplice. Ho dimenticato di rimuovere l'iniezione della cache - Ho modificato per correggere questo. –

Problemi correlati