Spring 忽略了 Apache Shiro Realm 类中的 @Transactional 注释

Posted

技术标签:

【中文标题】Spring 忽略了 Apache Shiro Realm 类中的 @Transactional 注释【英文标题】:Spring is ignoring @Transactional annotations in Apache Shiro Realm class 【发布时间】:2013-04-02 02:24:19 【问题描述】:

我正在使用 Spring 进行 IOC 和事务管理,并计划使用 Apache Shiro 作为安全库。

每当我想检查用户的权限时,我都会调用subject.isPermitted("right"),然后 Shiro 使用数据存储检查权限。在这些调用中,建立了一个数据库连接,并且我用@Transactional 注释了该方法。但是,每当我执行权限检查时,我总是收到一个错误,即没有绑定到线程的 Hibernate 会话。

该方法在 Realm 类中。我定义了一个自定义 Shiro 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 使用 Servlet 过滤器,所以我在 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>

我正在为 Spring 使用编程配置。这是我的 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;


总结一下这个问题,我相信我的 MainRealm 类已正确连接(它具有对 DAO 对象的 @Autowired 依赖项,并且我验证它不为空),但 @Transactional 注释除外。因此,我无法直接调用user.isPermitted(""),因为它会提示错误:No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

想寻求帮助以检查我是否遗漏了 Spring 配置中的任何内容。

与此同时,我通过在我的 Service 类的一个方法中调用 user.isPermitted("") 函数解决了这个问题,该方法被 @Transactional 正确绑定。

编辑当我检查 Spring 初始化的日志时,我可以看到:

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)

根据this SO answer,这意味着MainRealm 没有被事务管理器进行后处理,因此任何@Transactional 注释都会被忽略。如果是这样,我该如何纠正?

EDIT 2 根据this SO question:“换句话说,如果我编写自己的 BeanPostProcessor,并且该类直接引用上下文中的其他 bean,那么那些引用的 bean 将没有资格自动代理,并为此记录一条消息。”我刚刚检查了ShiroFilterFactoryBean,它实际上是一个 BeanPostProcessor。问题是它需要一个SecurityManager 实例,而后者又需要一个MainRealm 实例。所以这两个 bean 都是自动装配的,因此没有资格进行代理。我觉得我离解决方案更近了,但我仍然无法解决它。

【问题讨论】:

您可以尝试使用@Transactional@Transactional(readOnly=true) 注释将所有事务逻辑移动到某个UserService,并在您的MainRealm 中使用此服务。 我尝试将 Service 类自动装配到我的 Realm 中,但是,在这样做时,Service 类也遇到了 No hibernate session bound to thread 错误,并且它的 @Transactional 注释似乎不再起作用。似乎我在初始化 Shiro 对象时做错了什么。 【参考方案1】:

问题的根本原因其实是以下几点:

所有 BeanPostProcessor 及其直接引用的 bean 都将在启动时实例化...由于 AOP 自动代理是作为 BeanPostProcessor 本身实现的,因此没有 BeanPostProcessor 或直接引用的 bean 有资格进行自动代理(因此不会将方面“编织”到其中。

引用的 SO 问题是 here。

我通过将 Realm bean 创建与 SecurityManager bean 创建解耦解决了这个问题。

相关变化来自以下代码:

@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
public DefaultWebSecurityManager securityManager() 
    final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
    //sm.setRealm(mainRealm); -> set this AFTER Spring initialization so no dependencies
    return sm;

然后我使用 ServletContextListener 来侦听 Spring 上下文初始化完成的时间,并且我同时拥有 MainRealmSecurityManager bean。然后我只是把一个豆子塞进另一个豆子里。

@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注解可以在之前使用:

一个接口定义 接口方法 类定义 类的PUBLIC方法

如documentation中所述

您的方法是 protected 的事实一定是您的问题的原因,并且您的服务方法可能被声明为 public 这可以解释为什么它在这种情况下有效

【讨论】:

感谢您的提示。但是,我尝试了这个,但没有奏效。我认为这可能与我使用 Spring 错误配置 Shiro 对象有关。

以上是关于Spring 忽略了 Apache Shiro Realm 类中的 @Transactional 注释的主要内容,如果未能解决你的问题,请参考以下文章

apache-shiro 的怪事通过 spring-boot 集成到 spring-mvc 中

使用 Apache Shiro 的 Spring Boot

Spring Boot 桌面独立应用程序中的 Apache Shiro

Apache shiro的简单介绍与使用(与spring整合使用)

Spring shiro学习

在Spring MVC中使用Apache Shiro安全框架