SoWeLie
SoWeLie

Reputation: 1391

Spring MongoDB and Apache Shiro

I am attempting to use Apache Shiro with Spring and MongoDB. I am using Spring Data Repositories which are autowired. I have created my own custom realm for Shiro which uses a Spring Data repository to talk to Mongo:

public class PlatformRealm extends AuthorizingRealm {

    @Autowired(required = true)
    protected UserRepository userRepository = null;

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

The problem I'm seeing is the userRepository isn't being autowired. I get the following line in my console output referring to the PlatformRealm:

INFO  org.springframework.web.context.support.XmlWebApplicationContext  - Bean 'platformRealm' of type [class com.resonance.platform.core.security.PlatformRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

This is because of the Apache Shiro ShiroFilterFactoryBean. What is happening is this bean and all of its dependencies are being loaded up immediately when the container is started. It doesn't wait for my persistence beans to be initialized prior to resolving dependencies. This causes the repository reference to be null.

The following bean configurations are loaded via the contextConfigLocation parameter:

<context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value>
        /WEB-INF/web-platform-persistence.xml,
        /WEB-INF/web-platform-services.xml
    </param-value> 
</context-param> 

Services bean configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<bean id="userSession"
    class="com.resonance.platform.web.core.services.ShiroUserSessionService" />

<!-- Shiro (Security) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="/login" />
    <property name="successUrl" value="/" />
    <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter 
        bean -->
    <!-- defined will be automatically acquired and available via its beanName 
        in chain -->
    <!-- definitions, but you can perform instance overrides or name aliases 
        here if you like: -->
    <!-- <property name="filters"> <util:map> <entry key="anAlias" value-ref="someFilter"/> 
        </util:map> </property> -->
    <property name="filterChainDefinitions">
        <value>
            # some example chain definitions:
            /admin/** = passThruFilter, roles[admin]
            /** = passThruFilter
        </value>
    </property>
</bean>

<bean id="passThruFilter"
    class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter" />

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- Single realm app. If you have multiple realms, use the 'realms' property 
        instead. -->
    <property name="realm" ref="platformRealm" />
    <!-- By default the servlet container sessions will be used. Uncomment 
        this line to use shiro's native sessions (see the JavaDoc for more): -->
    <!-- <property name="sessionMode" value="native"/> -->
</bean>

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
    depends-on="lifecycleBeanPostProcessor" />

<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
</bean>

<!-- Define the Shiro Realm implementation you want to use to connect to 
    your back-end -->
<!-- security datasource: -->
<bean id="platformRealm" class="com.resonance.platform.core.security.PlatformRealm" />

Persistence bean config:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd
      http://www.springframework.org/schema/data/mongo
      http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/util
      http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<mongo:mongo id="mongo" />

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongo" />
    <constructor-arg value="platform" />
    <property name="writeConcern">
        <util:constant static-field="com.mongodb.WriteConcern.SAFE" ></util:constant>
    </property>
</bean>

<mongo:repositories base-package="com.resonance.platform.core.data.repositories" />

User Repository:

package com.resonance.platform.core.data.repositories;

import org.bson.types.ObjectId;
import org.springframework.data.repository.CrudRepository;

import com.resonance.platform.core.entities.User;

/**
 * A repository used to manage User entities.
 * @author Kyle
 */
public interface UserRepository extends CrudRepository<User, ObjectId> {

    /**
     * Gets a user by the specified login.
     * @param login
     * @return
     */
    User getByLogin(String login);

}

My question is, how can I get the userRepository dependency to resolved properly? I understand that the ShiroFilterFactoryBean has to be initialized before the other dependencies and whatnot, but there must be a way to get the userRepository dependency to be resolved.

EDIT: Added User Repository code.

Upvotes: 4

Views: 2995

Answers (6)

dnebing
dnebing

Reputation: 355

The ShiroFilterFactoryBean implements the BeanPostProcessor and, since it has dependencies on the security manager w/ its own dependencies on data stores, data access objects, etc. it can cause a whole slew of Bean X of type Y is not eligible for getting processed by all BeanPostProcessors messages.

The worst part is that it seems to be just a way to see the Filter implementations that Spring is instantiating in order to track and possibly inject properties into AuthorizationFilters.

Frankly I don't need that headache just for filter tracking, so I created a custom version that did not include the BeanPostProcessor. I'm now forced to manually wire in Filter implementations to the beans "filters" property, but at least I don't have to deal with that error and the questionable status of my security manager and related beans.

Upvotes: 0

cbopp
cbopp

Reputation: 505

Concrete problem explanation taken from ShiroFilterFactoryBean-and-a-spring-data-mongodb-realm:

The problem is that spring-data-mongodb requires a spring ApplicationEventMulticaster to have been initialised before it can be used.

ShiroFilterFactoryBean is a beanPostProcessor, and as such, during initialisation, spring attempts to configure its realms(and hence my realm and spring data mongo based userDao). it fails because ApplicationEventMulticaster has not yet been created.

After I've tried several suggested ways to solve this problem, like the InitializingBean, ApplicationContextAware or BeanPostProcessor interfaces (each resulting in a premature invocation, hence before initializing my necessary service/repository stuff), I came up with the following solution:

  1. Let spring create your shiro context without any automatic bean resolution to your services/repositories.
  2. Let spring create your service/repository context, including mongodb
  3. Create a simple class which will take care of your shiro-service coupling and configure it accordingly in your spring config. This class will be invoked after your shiro and service context has been successful set up.

To (1), sth. like this:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="filterChainDefinitions">
        <value>
            <!-- Your definitions -->
        </value>
    </property>
</bean>

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"
        p:realm-ref="myShiroRealm" />

<bean id="myShiroRealm" class="com.acme.MyShiroRealm" 
    <!--no bean refs here-->
/>

To (2), sth. like this:

<bean id="myService" class="com.acme.MyService"
        c:myRepository-ref="myRepository" />

...

<!-- Ask Spring Data to scan our repositories -->
<mongo:repositories base-package="com.acme.repository.impl.mongodb" />

To (3):

public class ShiroRealmServiceBridge {
    public static void postInject( MyShiroServerRealm realm, MyService service ) {
        realm.setService( service );
    }
}

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass"><value>com.acme.ShiroRealmServiceBridge</value></property>
    <property name="targetMethod"><value>postInject</value></property>
    <property name="arguments">
    <list>
        <ref bean="myShiroRealm" />
        <ref bean="myService" />
    </list>
</property>

Advantages:

  • It works xD
  • No additional burden/dependencies on your shiro stuff
  • Complete spring configuration and setup, resulting in a consistent state after initialization

Disadvantage:

  • One time overhead setup
  • May result in an inconsistent state, which will complain at runtime rather than at startup, if you forget or bump the glue-configuration

Upvotes: 0

Andrew Weiland
Andrew Weiland

Reputation: 11

I've had this problem too. It has something to do with the order of bean initialization in the Spring container. The workaround is not to autowire the repository but have your realm implement ApplicationContextAware and get the needed beans straight from the context. It's not elegant, but it'll work.

Upvotes: 1

Jiggy
Jiggy

Reputation: 41

I am running into the same problem described here. I am noticing two spring factories.

  1. from the dispacher-servlet.xml which loads @Service @Repository classes due to component-scan defined at an base package level so I can @Autowire Service class into Controller.
  2. from application context doesn't seem to @Autowire classes marked as @Service because they are not loaded.

Upvotes: 2

user1323865
user1323865

Reputation: 445

I am not too sure if this is helpful, but you may check this question from me for an alternative solution.

But, the core issue probably still stays open.

Upvotes: 0

FelixM
FelixM

Reputation: 1506

If I understand you right you should be able to create a subclass of ShiroFilterFactoryBean which implements org.springframework.beans.factory.InitializingBean. In InitializingBean.afterPropertiesSet() you would then add some code that gets the UserRepository and sets it to that field. Not the most elegant solution, but this looks like an exceptional case.

Upvotes: 1

Related Questions