Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
374 views
in Technique[技术] by (71.8m points)

jakarta ee - Inject CDI managed bean in custom Shiro AuthorizingRealm

In an app I'm building we're using straight Java 6 EE and JBoss (no Spring, etc), with JPA/Hibernate, JSF, CDI and EJBs.

I haven't found many good general security solutions (recommendations are welcome), but the best bet I found is Apache Shiro.

However this seems to have a number of shortcomings. Some of which you can read about at Balus C's site:

http://balusc.blogspot.com/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html

But I've stumbled on another big problem which is already mentioned here regarding dependency injection and proxying.

Basically I have a nicely written JPA-based UserDAO that provides everything necessary for authentication. My database is neatly configured in persistence.xml and mydatabase-ds.xml (for JBoss).

It seems silly to duplicate all this config info a second time and add user tables queries into shiro.ini. So this is why I have opted to write my own Realm instead of using JdbcRealm.

My first attempt at this was to subclass AuthorizingRealm...something like:

@Stateless
public MyAppRealm extends AuthorizingRealm {
    @Inject private UserAccess userAccess;

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

        UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;

        User user = userAccess.getUserByEmail(userPassToken.getUsername());
        if (user == null) {
            return null;
        }

        AuthenticationInfo info = new SimpleAuthenticationInfo();
        // set data in AuthenticationInfo based on data from the user object

        return info;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // TODO
        return null;
    }
}

So this fails pretty bad, because MyAppRealm cannot be proxied because there is a final init() method in a parent class up the class hierarchy.

My second attempt was to have MyAppRealm implement all the needed interfaces and just delegate them to instance of AuthorizingRealm. I didn't like this, but might as well give it a try.

This gets me further, the webapp starts up, but still falls short. The reason is in the config file, shiro.ini, I specify the class for my realm:

myAppRealm = com.myapp.MyAppRealm

This pretty much tells me that Shiro will be responsible for creating the MyAppRealm instance. Therefore it will not be CDI managed and thus not injected, which is exactly what I'm seeing.

I've seen this SO answer, but I don't see how it could possibly work because again a subclass of AuthorizingRealm will inherit a final init() method meaning the subclass cannot be proxied.

Any thoughts on how I can get around this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...