在自定义Shiro AuthorizingRealm中注入CDI托管bean [英] Inject CDI managed bean in custom Shiro AuthorizingRealm

查看:1007
本文介绍了在自定义Shiro AuthorizingRealm中注入CDI托管bean的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在构建的应用程序中,我们使用Java 6 EE和JBoss(没有Spring等),使用JPA / Hibernate,JSF,CDI和EJB。

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.

我没有找到很多好的通用安全解决方案(欢迎推荐),但我发现的最好的选择是Apache Shiro。

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

然而这似乎有一个缺点的数量。您可以在 Balus C's 网站:

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.

基本上我有一个写得很好的基于JPA的UserDAO,它提供了身份验证所需的一切。我的数据库在persistence.xml和mydatabase-ds.xml(对于JBoss)中整齐地配置。

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

再次复制所有这些配置信息并添加用户似乎很愚蠢表查询到shiro.ini。所以这就是我选择编写自己的Realm而不是使用JdbcRealm的原因。

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.

我的第一次尝试就是将AuthorizingRealm子类化了......类似于:

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

所以这很糟糕,因为MyAppRealm无法代理因为类层次结构中的父类中有一个最终的init()方法。

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.

我的第二次尝试是让MyAppRealm实现所有需要的接口并将它们委托给AuthorizingRealm的实例。我不喜欢这样,但不妨尝试一下。

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.

这让我更进一步,webapp启动了,但仍然不足。原因是在配置文件shiro.ini中,我为我的领域指定了类:

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

这几乎告诉我Shiro将负责创建MyAppRealm实例。因此它不会被CDI管理,因此不会被注入,这正是我所看到的。

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.

我已经看到这样SO answer ,但我看不出它是如何工作的,因为再次,AuthorizingRealm的子类将继承最终的init()方法,这意味着子类无法代理。

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?

推荐答案

您可以通过初始化您的领域作为应用程序启动生命周期的一部分,然后让Shiro通过JNDI名称检索它查找。

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.

使用@Singleton和 @ Startup 在应用程序生命周期中尽早强制创建它。在这个类中,您将实例化MyAppRealm类的新实例,并提供注入的UserAccess引用作为构造参数。这意味着你必须更新你的MyAppRealm类来获取这个新的构造函数参数。

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

更新 shiro.ini 如下:

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

此方法可让您访问所有CDI托管bean而无需必须利用CDI的内部运作。这样做的原因是因为在Web层出现之后才加载'shiro.ini',这是在初始化CDI和EJB框架之后。

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.

这篇关于在自定义Shiro AuthorizingRealm中注入CDI托管bean的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆