2013-04-10 11 views
6

Sto utilizzando Spring per IOC e gestione delle transazioni e sto pensando di utilizzare Apache Shiro come libreria di sicurezza.Spring ignora le annotazioni @Transactional nella classe Apache Shiro Realm

Ogni volta che si desidera verificare le autorizzazioni di un utente, chiamo subject.isPermitted("right"), dopo di che Shiro verifica l'autorizzazione utilizzando un datastore. All'interno di queste chiamate, viene stabilita una connessione al database e ho annotato il metodo con @Transactional. Tuttavia, viene sempre visualizzato un messaggio di errore che indica che la sessione di Hibernate non è legata al thread ogni volta che eseguo il controllo del permesso.

Il metodo è nella classe Realm. Ho definito una consuetudine Shiro classe Realm:

@Component 
public class MainRealm extends AuthorizingRealm { 

@Autowired 
protected SysUserDao userDao; 

@Transactional 
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
     throws AuthenticationException { 
    ... 
    final SysUser user = this.userDao.findByUsername(un); 
    ... 
    return authInfo; 
} 

@Transactional 
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 
    ... 
    permissions = this.userDao.getAccessRights(un); 
    ... 
    return authInfo; 
} 
} 

Apache Shiro utilizza un filtro Servlet, così Ho il seguente definito in web.xml:

<filter> 
    <filter-name>shiroFilter</filter-name> 
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
    <init-param> 
     <param-name>targetFilterLifecycle</param-name> 
     <param-value>true</param-value> 
    </init-param> 
</filter> 

Sto usando la configurazione programmatica per la primavera. Qui è la mia classe App Config:

@Configuration //Replaces Spring XML configuration 
@ComponentScan(basePackages = "com.mycompany") 
@EnableTransactionManagement //Enables declarative Transaction annotations 
public class SpringAppConfig { 

@Bean 
public DataSource sqlServerDataSource() throws Exception {...} 
@Bean 
@Autowired 
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {...} 
@Bean 
public AnnotationSessionFactoryBean getSessionFactory() throws Exception {...} 
@Bean 
public static PersistenceExceptionTranslationPostProcessor exceptionTranslation() {...} 

@Bean 
@Autowired 
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) { 
    final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash); 
    hcm.setHashIterations(shiroIter); 
    hcm.setStoredCredentialsHexEncoded(shiroHexEncoded); 
    mainRealm.setCredentialsMatcher(hcm); 
    final DefaultWebSecurityManager sm = new DefaultWebSecurityManager(); 
    sm.setRealm(mainRealm); 
    return sm; 
} 

@Bean 
@Autowired 
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { 
    final ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); 
    filter.setSecurityManager(securityManager); 
    return filter; 
} 

/** 
* This method needs to be static due to issues defined here:<br> 
* https://issues.apache.org/jira/browse/SHIRO-222 
*/ 
@Bean 
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 
    LifecycleBeanPostProcessor lbpp = new LifecycleBeanPostProcessor(); 
    return lbpp; 
} 

@Bean 
@DependsOn("lifecycleBeanPostProcessor") 
public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { 
    return new DefaultAdvisorAutoProxyCreator(); 
} 

@Bean 
@Autowired 
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager secMan) { 
    AuthorizationAttributeSourceAdvisor advBean = new AuthorizationAttributeSourceAdvisor(); 
    advBean.setSecurityManager(secMan); 
    return advBean; 
} 
} 

In sintesi il problema, credo che la mia classe MainRealm viene collegata correttamente (ha una dipendenza @Autowired a un oggetto DAO e ho verificato che non è nullo) con la eccezione dell'annotazione @Transactional. A causa di ciò, non posso chiamare direttamente user.isPermitted("") quando viene visualizzato un messaggio di errore: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.

Vorrei chiedere aiuto per verificare se ho perso qualcosa nella mia configurazione di Primavera.

Nel frattempo ho risolto il problema chiamando la funzione user.isPermitted("") all'interno di un metodo nella mia classe Servizio che è correttamente associato da un @Transactional.

EDIT Quando ho controllato i registri per l'inizializzazione primavera posso vedere questo:

Bean 'mainRealm' of type [class com.x.security.MainRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 

Secondo this SO answer significa MainRealm non è essere postelaborati dal gestore di transazioni pertanto eventuali annotazioni @Transactional vengono ignorati. In tal caso, come posso correggerlo?

EDIT 2 Secondo this SO question: "In altre parole, se scrivo la mia BeanPostProcessor, e quella classe fa riferimento direttamente altri fagioli nel contesto, quindi quei fagioli riferimento non potranno beneficiare di auto-proxy, e un messaggio è registrato in tal senso. " Ho appena controllato ShiroFilterFactoryBean ed è in effetti un BeanPostProcessor. E il problema è che richiede un'istanza SecurityManager che a sua volta richiede un'istanza MainRealm. Quindi, entrambi i bean sono autowired e quindi non sono eleggibili per il proxy. Mi sento come se fossi più vicino alla soluzione, ma non riesco ancora a risolverlo.

+0

Si può provare a spostare ogni logica transazionale ad alcuni '' UserService' con @ Transactional' o '@Transactional (readOnly = true)' annotazioni e utilizzare questo servizio all'interno del vostro 'MainRealm'. – sody

+0

Ho provato a convertire una classe di servizio nel mio reame, tuttavia, dopo averlo fatto, anche la classe Service ha riscontrato l'errore 'No hibernate session bound to thread' e le sue annotazioni @Transactional non sembravano funzionare più. Sembra che sto facendo qualcosa di sbagliato nell'inizializzare gli oggetti Shiro. –

risposta

4

La causa principale del problema è infatti a causa del seguente:

Tutti BeanPostProcessors e loro fagioli direttamente riferimento vengono istanziati all'avvio ... Dal AOP auto-proxy è implementato come un BeanPostProcessor per sé, non BeanPostProcessors o fagioli direttamente riferimento sono ammissibili per auto-proxy (e quindi non avranno aspetti "tessuti" in loro.

La domanda SO di riferimento è here.

Ho risolto questo problema disaccoppiando la creazione del bean Realm dalla creazione del bean SecurityManager.

La variazione rilevante è dal codice seguente:

@Bean 
@Autowired 
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) { 
    final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash); 
    hcm.setHashIterations(shiroIter); 
    hcm.setStoredCredentialsHexEncoded(shiroHexEncoded); 
    mainRealm.setCredentialsMatcher(hcm); 
    final DefaultWebSecurityManager sm = new DefaultWebSecurityManager(); 
    sm.setRealm(mainRealm); 
    return sm; 
} 

al codice seguente:

@Bean 
public DefaultWebSecurityManager securityManager() { 
    final DefaultWebSecurityManager sm = new DefaultWebSecurityManager(); 
    //sm.setRealm(mainRealm); -> set this AFTER Spring initialization so no dependencies 
    return sm; 
} 

allora uso un ServletContextListener che ascolta quando l'inizializzazione contesto Spring completa e ho la entrambi i fagioli MainRealm e SecurityManager. Quindi inserisco solo un fagiolo all'interno di un altro.

@Override 
public void contextInitialized(ServletContextEvent sce) { 
    try { 

     //Initialize realms 
     final MainRealm mainRealm = (MainRealm)ctx.getBean("mainRealm"); 
     final DefaultWebSecurityManager sm = (DefaultWebSecurityManager)ctx.getBean("securityManager"); 
     sm.setRealm(mainRealm); 
    } catch (Exception e) { 
     System.out.println("Error loading: " + e.getMessage()); 
     throw new Error("Critical system error", e); 
    } 
} 
2

@Transactional annotazione può essere utilizzato prima:

  • Un'interfaccia def
  • un metodo di interfaccia
  • Una classe def
  • Un PUBBLICO metodo di una classe

Come spiegato nello documentation

Il fatto che il vostro metodo è protected deve essere il motivo del problema, e il metodo di servizio è stato forse dichiarato come public che avrebbe spiegato perché ha funzionato in quel caso

+0

Grazie per il suggerimento. Tuttavia, ho provato questo e non ha funzionato. Penso che questo potrebbe essere un problema relativo ai miei oggetti di Shiro che configurano male con Spring. –