当前位置: 首页 > 知识库问答 >
问题:

在自定义Shiro授权域中注入CDI托管bean

葛鸿轩
2023-03-14

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

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

然而,这似乎有一些缺点。您可以在Balus C的网站上阅读其中一些内容:

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

但是我偶然发现了另一个大问题,这里已经提到了依赖注入和代理。

基本上,我有一个写得很好的基于JPA的UserDAO,它提供了身份验证所需的一切。我的数据库在持久性中配置得很好。xml和MyDS数据库。xml(用于JBoss)。

第二次复制所有这些配置信息并将用户表查询添加到shiro.ini.似乎很傻,所以这就是为什么我选择编写自己的领域而不是使用JdbcRealm。

我的第一次尝试是将AuthorizationRealm子类化。。。比如:

@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()方法。

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

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

myAppRealm = com.myapp.MyAppRealm

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

我已经看到了这样的答案,但我不知道它是如何工作的,因为AuthorizationRealm的子类将再次继承一个final init()方法,这意味着该子类不能被代理。

你有没有想过我该怎么解决这个问题?

共有2个答案

郎永福
2023-03-14

这是一个典型的问题:你有两个不同的框架,它们都想管理对象的生命周期,你需要让它们相互作用,但是两者都坚持拥有完全的控制权(我对这一点的印象有点像哥斯拉和Gamera在东京市中心的战斗)。你可能不会马上想到Shiro是CDI的竞争对手,但是因为它创建了对象的实例,它本质上包含了一个微小的、基本的依赖注入框架(也许这是Greenspun第十条规则的DI版本)。我遇到过一个类似的问题,使Web框架与CDI交互,这些框架创建并注入其支持bean的实例

解决这个问题的一种方法是在两个框架之间建立一个明确的桥梁。如果你真的很幸运,非CDI框架将有钩子让你定制对象创建,你可以在其中插入使用CDI的东西(例如在Stripes web框架中,你可以编写一个使用CDI的ActionResolver)。

如果不是,则网桥必须采用代理的形式。在该代理中,您可以执行显式CDI查找。您可以通过掌握BeanManager引导到CDI,它允许您设置上下文,然后在其中创建bean。大概是这样的:

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

userDAO是一个注入的、CDI管理的bean,绑定到您现在作为creationalContext持有的上下文。

当您完成bean时(取决于您是否在每个请求或每个应用程序生命周期中执行一次此查找),使用以下内容释放bean:

creationalContext.release();
王辉
2023-03-14

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

使用@Singleton和@Startup创建一个安装bean,以便在应用程序生命周期中尽早创建它。在这个类中,您将实例化“MyAppRealm”类的一个新实例,并提供一个注入的UserAccess引用作为构造参数。这意味着你必须更新你的“MyAppRealm”类来接受这个新的构造函数参数。

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层出现之前,不会加载ini。

 类似资料:
  • 我正试图将我的UserDAO注入到ApacheShiro正在使用的自定义授权域中,但是。。。我得到空值。 我做错了什么? 西罗。伊尼 jparelm。JAVA 我必须做些什么才能让CDI在我的自定义域中意识到@Inject并正确地注入我的UserDAO?

  • 我正在将Apache Shiro实现到我的web应用程序中,在启动时遇到了问题。 我想从postgreSQL数据库加载角色和权限,并检查用户是否具有角色/权限。 我的角色权利系统包括以下内容: 用户- 角色- 可以将角色分配给多个用户 用户角色分配始终与其他两个条件相关: 一个组织单位(在我的例子中称为OE) 一个学院 因此,用户“教员管理员”基本上可能有以下内容: 角色OE-管理员,拥有OE:3

  • 功能说明 目睹直播提供了一系列的授权观看方式,如密码验证,付费观看等,然而由于客户业务的多样性,实现如:接入客户自身账户体系,登陆OA系统后再跳转到目睹观看直播等一系列更高级的授权观看方式,此时就需要使用自定义授权。 自定义授权逻辑图 功能设置 首先,需在 某个频道 - 频道管理 - 授权观看 - 授权类型 中点击自定义授权,并输入您希望在观众进入观看页时跳转的链接,如: http://your-

  • 我正在使用Weld作为基础CDI框架实现。 我有一个类ClassWithoutControl,它没有一个no-arg构造函数(也就是不是一个有效的Bean)。 我为该类创建了一个自定义Bean。 在触发AfterBeanDiscovery事件时,我通过扩展添加此bean。 当对这个bean调用create(creationalcontext<>ctx)时,我构造了ClassWithoutContr

  • 我尝试设置Firebase身份验证,在localhost上它也起作用了。但是当我在Firebase上获得应用程序后,我会得到错误。 我已经在Firebase看过了,域是白名单。 我使用Google登录firebase的代码: 以下是完整的错误消息:

  • 我目前正在开发一个尽可能尊重六边形架构原则的应用程序。 因此,我的“域”模块(组Id: ; 工件Id:)不依赖于任何技术框架。 我的所有服务都使用自定义注释(本身是我域的一部分)进行注释: 然而,在我的“Quarkus应用”模块(groupId:< code > acme ;artifact id:< code > app-quar kus ,我需要注入我的“域”模块中定义的服务(< code>a