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

查看:40
本文介绍了在自定义 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 的 网站:

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.

我看过这个答案,但我不知道它是如何工作的,因为 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 的内部工作原理.这样做的原因是因为在 CDI 和 EJB 框架初始化之后 Web 层出现之前,'shiro.ini' 不会被加载.

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天全站免登陆