Inject CDI managed bean in custom Shiro AuthorizingRealm

拈花ヽ惹草 提交于 2019-11-28 08:31:15
justin.hughey

You can do this by initializing your realm as a part of the start-up life cycle of the application and then have Shiro retrieve it via JNDI name lookup.

Create a setup bean with @Singleton and @Startup to force its creation as early as possible in the application life cycle. In this class you'll be instantiating a new instance of your "MyAppRealm" class and providing an injected UserAccess reference as a construction parameter. Which means you'll have to update your "MyAppRealm" class to take this new constructor parameter.

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;
  }
}

Update shiro.ini as follows:

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

This approach will provide you access to all of your CDI managed beans without having to leverage the inner workings of CDI. The reason this works is because the 'shiro.ini' isn't loaded until the web layer comes up which is after initialization of the CDI and EJB frameworks.

This is a classic problem: you have two different frameworks which both want to manage object lifecycles, and you need to make them interact, but both insist on having complete control (my mental image of this is something like Godzilla and Gamera battling in downtown Tokyo). You might not immediately think of Shiro as being a competitor to CDI, but because it creates instances of its objects, it essentially contains a tiny, rudimentary dependency injection framework (perhaps this is a DI version of Greenspun's tenth rule). I have encountered a similar problem making web frameworks, which create and inject instances of their backing beans, interact with CDI.

An approach to solving this is to create an explicit bridge between the two frameworks. If you're really lucky, the non-CDI framework will have hooks to let you customise object creation, into which you can plug something that uses CDI (eg in the Stripes web framwork, you can write an ActionResolver which uses CDI).

If not, then the bridge must take the form of a proxy. Within that proxy, you can do an explicit CDI lookup. You can bootstrap into CDI by getting hold of the BeanManager, which lets you set up a context and then create beans in it. Something like this:

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);

The userDAO is an injected, CDI-managed bean bound to the context you now hold as creationalContext.

When you're finished with the bean (it's up to you if you do this lookup once per request or once per application lifetime), release the bean with:

creationalContext.release();
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!