Question

I'm creating a rest api using Jersey 2.5.1. I'm using HK2 for dependency injection. Later on I decided to use Apache Shiro for authentication and authorization.

While creating my own custom Shiro Realm I ran into some problems. In my realm I wanted to inject a dependency. However, when I ran my application the dependency was not resolved.

Here is my setup:

web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>MyApplication</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>my.app.api.MyApplication</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>MyApplication</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

shiro.ini

[main]

authcBasicRealm = my.app.api.MyCustomRealm
matcher = my.app.api.MyCustomCredentialsMatcher
authcBasicRealm.credentialsMatcher = $matcher
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager

[urls]

/** = authcBasic

MyApplication.java

public class MyApplication extends ResourceConfig {
   public MyApplication() {
      register(new ApplicationBinder());
      packages(true, "my.app.api");
   }
}

ApplicationBinder.java

public class ApplicationBinder extends AbstractBinder {
   @Override
   protected void configure() {
      bind(UserDAO.class).to(new TypeLiteral<Dao<User>>(){});
      bind(RealDatasource.class).to(DataSource.class);
   }
}

MyCustomRealm.java

public class MyCustomRealm extends JdbcRealm {

   @Inject DataSource source;

   public MyCustomRealm() {
      super();
   }

   @PostConstruct
   private void postConstruct() {
      // postConstruct is never executed
      setDataSource(source);
   }
}




So, the problem is that source is not injected in MyCustomRealm. All other classes that isn't created by Shiro gets its dependencies injected. Could the problem be that Shiro is creating my CustomRealm via the ini file?

Was it helpful?

Solution

I ran into a similar issue, and, while this is probably no longer an issue for you, I wanted to provide the work-around I used.

The issue is ownership of MyCustomRealm. It is being created by shiro in the org.apache.shiro.web.env.EnvironmentLoaderListener by reading the ini file which is outside the scope of the hk2 provider in the Jersey servlet.

Dependency injection is only done when the object is being provided by hk2's ServiceLocator--shiro has no knowledge of this locator and only constructs an instance of MyCustomRealm with its default constructor.

I worked around this by implementing a org.glassfish.jersey.server.spi.ContainerLifecycleListener that gets a handle to the ServiceLocator and shiro's SecurityManager (through the ServletContext which is registered with the ServiceLocator). It then manually injects the data into the realm created by shiro.

If you're interested, I can post the code as a gist.

OTHER TIPS

One problem I see in MyCustomRealm is that you are expecting DataSource to be filled in at construction time. There are two ways to resolve this issue; one is to use constructor injection and the other is to use a post construct. Here would be using constructor injection:

public class MyCustomRealm extends JdbcRealm {

   private final DataSource source;

   @Inject
   public MyCustomRealm(DataSource source) {
      super();

      this.source = source;

      // source does not get injected
      setDataSource(source);
   }
}

Here is how you would do it with postConstruct:

public class MyCustomRealm extends JdbcRealm {

   @Inject DataSource source;

   public MyCustomRealm() {
      super();
   }

   @javax.annotation.PostConstruct
   private void postConstruct() {
      // source does not get injected
      setDataSource(source);
   }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top