2013-08-29 14 views
11

In un'app che sto costruendo usiamo Java 6 EE e JBoss diritti (no Spring, ecc.), Con JPA/Hibernate, JSF, CDI ed EJB.Inietta bean gestito CDI in Shiro personalizzato AuthorizingRealm

Non ho trovato molte buone soluzioni di sicurezza generale (i consigli sono benvenuti), ma la soluzione migliore che ho trovato è Apache Shiro.

Tuttavia questo sembra avere un numero di difetti. Alcuni dei quali si può leggere su a Balus C's sito:

http://balusc.blogspot.com/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html

Ma mi hai inciampato su un altro grosso problema che è già menzionato here per quanto riguarda l'iniezione di dipendenza e il proxy.

Fondamentalmente ho un UserDAO basato su JPA ben scritto che fornisce tutto il necessario per l'autenticazione. Il mio database è ben configurato in persistence.xml e mydatabase-ds.xml (per JBoss).

Sembra stupido duplicare tutte queste informazioni di configurazione una seconda volta e aggiungere query alle tabelle utente in shiro.ini. Quindi è per questo che ho deciso di scrivere il mio Realm invece di usare JdbcRealm.

Il mio primo tentativo di questo è stato quello di creare una sottoclasse AuthorizingRealm ... qualcosa di simile:

@Stateless 
public MyAppRealm extends AuthorizingRealm { 
    @Inject private UserAccess userAccess; 

    @Override 
    protected AuthenticationInfo doGetAuthenticationInfo(
     AuthenticationToken token) throws AuthenticationException { 

     UsernamePasswordToken userPassToken = (UsernamePasswordToken) token; 

     User user = userAccess.getUserByEmail(userPassToken.getUsername()); 
     if (user == null) { 
      return null; 
     } 

     AuthenticationInfo info = new SimpleAuthenticationInfo(); 
     // set data in AuthenticationInfo based on data from the user object 

     return info; 

    @Override 
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 
     // TODO 
     return null; 
    } 
} 

Quindi questo non riesce piuttosto male, perché MyAppRealm non può essere approssimata perché c'è un init finale() metodo in una classe genitore la gerarchia delle classi.

Il mio secondo tentativo è stato quello di fare in modo che MyAppRealm implementasse tutte le interfacce necessarie e delegarle semplicemente a istanza di AuthorizingRealm. Non mi è piaciuto, ma potrebbe anche provarci.

Questo mi porta oltre, la webapp si avvia, ma non riesce ancora a farcela. La ragione è nel file di configurazione, shiro.ini, ho specificare la classe per il mio regno:

myAppRealm = com.myapp.MyAppRealm 

Questo più o meno mi dice che Shiro sarà responsabile della creazione dell'istanza MyAppRealm. Pertanto non sarà gestito da CDI e quindi non iniettato, che è esattamente quello che sto vedendo.

Ho visto questo SO answer, ma non vedo come potrebbe funzionare perché ancora una sottoclasse di AuthorizingRealm erediterà un metodo init() finale che significa che la sottoclasse non può essere proxy.

Qualche idea su come posso aggirare questo?

risposta

8

Questo è un classico problema: ci sono due quadri diversi, che sia desidera gestire i cicli di vita degli oggetti, ed è necessario farli interagire, ma entrambi insistere per avere il controllo completo (la mia immagine mentale di questo è qualcosa di simile a Godzilla e Gamera combattendo nel centro di Tokyo). Potresti non pensare immediatamente a Shiro come a un concorrente di CDI, ma poiché crea istanze dei suoi oggetti, esso contiene essenzialmente un framework di iniezione di dipendenza minuscolo e rudimentale (forse questa è una versione DI di Greenspun's tenth rule). Ho riscontrato un problema simile che creava framework web, che creano e iniettano istanze dei loro backing bean, interagiscono con CDI.

Un approccio per risolvere questo è creare un ponte esplicito tra i due framework.Se sei davvero fortunato, il framework non-CDI avrà dei ganci che ti permetteranno di personalizzare la creazione dell'oggetto, in cui puoi collegare qualcosa che usa CDI (ad esempio nel framwork web di Stripes, puoi scrivere un ActionResolver che usa CDI).

In caso contrario, il bridge deve assumere la forma di un proxy. All'interno di quel proxy, puoi fare una ricerca CDI esplicita. È possibile eseguire il bootstrap in CDI afferrando lo BeanManager, che consente di impostare un contesto e quindi creare bean in esso. Qualcosa di simile a questo:

BeanManager beanManager = (BeanManager) new InitialContext().lookup("java:comp/BeanManager"); 
Bean<UserDAO> userDAObean = (Bean<UserDAO>) beanManager.resolve(beanManager.getBeans(UserDAO.class)); 
CreationalContext<?> creationalContext = beanManager.createCreationalContext(null); 
UserDAO userDAO = userDAObean.create(creationalContext); 

Il userDAO è un iniettato, fagioli CDI gestiti legata al contesto avete ora come creationalContext.

Quando hai finito con il fagiolo (sta a voi se fate questo ricerca una volta per ogni richiesta o una volta per tutta la vita di applicazione), rilasciare il fagiolo con:

creationalContext.release(); 
+0

Grazie per la risposta. Questa è un'ottima risposta teorica e in questo senso colpisce l'unghia sulla testa. Penso di aver costruito con successo un 'bridge' usando beanmanager. Non è esattamente carino, ma spero di evolverlo e perfezionarlo nel tempo. – lostdorje

8

È possibile effettuare questa operazione di inizializzazione il tuo reame come parte del ciclo di vita di avvio dell'applicazione e poi Shiro lo recupera tramite la ricerca del nome JNDI.

Creare un bean di installazione con @Singleton e @Startup per forzare la sua creazione il prima possibile nel ciclo di vita dell'applicazione. In questo corso verrà istanziata una nuova istanza della classe "MyAppRealm" e verrà fornito un riferimento UserAccess inserito come parametro di costruzione. Il che significa che dovrai aggiornare la tua classe "MyAppRealm" per prendere questo nuovo parametro costruttore.

import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.annotation.PostConstruct; 
import javax.annotation.PreDestroy; 
import javax.ejb.EJB; 
import javax.ejb.Singleton; 
import javax.ejb.Startup; 
import javax.naming.InitialContext; 
import javax.naming.NamingException; 

@Singleton 
@Startup 
public class ShiroStartup { 

    private final static String CLASSNAME = ShiroStartup.class.getSimpleName(); 
    private final static Logger LOG = Logger.getLogger(CLASSNAME); 

    public final static String JNDI_REALM_NAME = "realms/myRealm"; 

    // Can also be EJB... 
    @Inject private UserAccess userAccess; 

    @PostConstruct 
    public void setup() { 
    final UserAccess service = getService(); 
    final Realm realm = new MyAppRealm(service); 

    try { 
     // Make the realm available to Shiro. 
     bind(JNDI_REALM_NAME, realm); 
    } 
    catch(NamingException ex) { 
     LOG.log(Level.SEVERE, "Could not bind realm: " + JNDI_REALM_NAME, ex); 
    } 
    } 

    @PreDestroy 
    public void destroy() { 
    try { 
     unbind(JNDI_REALM_NAME); 
    } 
    catch(NamingException ex) { 
     LOG.log(Level.SEVERE, "Could not unbind realm: " + JNDI_REALM_NAME, ex); 
    } 
    } 

    /** 
    * Binds a JNDI name to an object. 
    * 
    * @param jndi The JNDI name. 
    * @param object The object to bind to the JNDI name. 
    */ 
    private static void bind(final String jndi, final Object object) 
    throws NamingException { 
    final InitialContext initialContext = createInitialContext(); 

    initialContext.bind(jndi, object); 
    } 

    private static void unbind(final String name) throws NamingException { 
    final InitialContext initialContext = createInitialContext(); 

    initialContext.unbind(name); 
    } 

    private static InitialContext createInitialContext() throws NamingException { 
    return new InitialContext(); 
    } 

    private UserAccess getService() { 
    return this.userAccess; 
    } 
} 

Aggiornamento shiro.ini come segue:

realmFactory = org.apache.shiro.realm.jndi.JndiRealmFactory 
realmFactory.jndiNames = realms/myRealm 

Questo approccio fornirà di accedere a tutti i tuoi CDI gestito fagioli senza dover sfruttare il funzionamento interno di CDI. Il motivo per cui questo funziona è perché lo 'shiro.ini' non viene caricato fino a quando non viene visualizzato il livello Web che è dopo l'inizializzazione dei framework CDI ed EJB.

+0

e come si può quindi assegnare i domini forniti dalla Fabbrica al securitymanager? – billdoor

+0

La voce 'shiro.ini' sopra lo gestisce. Dai un'occhiata al codice sorgente di "org.apache.shiro.config.IniSecurityManagerFactory" se vuoi vedere come lo fa Shiro. –

Problemi correlati