13

私が構築しているアプリでは、JPA/Hibernate、JSF、CDI、EJB とともに、ストレートな Java 6 EE と JBoss (Spring などは使用しない) を使用しています。

優れた一般的なセキュリティ ソリューションはあまり見つかりませんでしたが (推奨は大歓迎です)、見つけた最善の策は Apache Shiro です。

しかし、これには多くの欠点があるようです。Balus C のサイトでその一部を読むことができます。

https://balusc.omnifaces.org/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html

しかし、依存性注入とプロキシに関して、ここで既に言及されている別の大きな問題に遭遇しました。

基本的に、認証に必要なすべてを提供する適切に作成された JPA ベースの UserDAO があります。私のデータベースは、persistence.xml と mydatabase-ds.xml (JBoss 用) できちんと構成されています。

このすべての構成情報をもう一度複製して、ユーザー テーブルのクエリを shiro.ini に追加するのはばかげているようです。これが、JdbcRealm を使用する代わりに独自の Realm を作成することを選択した理由です。

これに対する私の最初の試みは、AuthorizingRealm をサブクラス化することでした...次のようなものです。

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

クラス階層の上の親クラスに最終的な init() メソッドがあるため、 MyAppRealm をプロキシできないため、これはかなり失敗します。

私の 2 番目の試みは、MyAppRealm に必要なすべてのインターフェイスを実装させ、それらを AuthorizingRealm のインスタンスに委譲することでした。私はこれが好きではありませんでしたが、試してみてください。

これでさらに進みます。webapp は起動しますが、まだ不十分です。その理由は、構成ファイル shiro.ini にあり、レルムのクラスを指定します。

myAppRealm = com.myapp.MyAppRealm

これは、Shiro が MyAppRealm インスタンスの作成を担当することを示しています。したがって、CDI で管理されないため、注入されません。これはまさに私が見ているものです。

この SO answerを見てきましたが、 AuthorizingRealm のサブクラスが最終的な init() メソッドを継承するため、サブクラスをプロキシできないため、どのように機能するかわかりません。

これを回避する方法について何か考えはありますか?

4

2 に答える 2

9

これを行うには、アプリケーションの起動ライフ サイクルの一部としてレルムを初期化し、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 の内部動作を利用することなく、すべての CDI マネージド Bean にアクセスできるようになります。これが機能する理由は、CDI および EJB フレームワークの初期化後に Web レイヤーが起動するまで「shiro.ini」がロードされないためです。

于 2014-12-01T14:41:39.737 に答える
8

これは古典的な問題です。オブジェクトのライフサイクルを管理したい 2 つの異なるフレームワークがあり、それらを相互作用させる必要がありますが、どちらも完全に制御することを主張しています (これに対する私のイメージは、東京のダウンタウンでゴジラとガメラが戦っているようなものです)。 )。Shiro が CDI の競争相手であるとはすぐには思わないかもしれませんが、Shiro はそのオブジェクトのインスタンスを作成するため、基本的に小さな初歩的な依存性注入フレームワークを含んでいます (おそらく、これはGreenspun の 10 番目の規則のDI バージョンです)。バッキング Bean のインスタンスを作成および注入する Web フレームワークを作成して、CDI とやり取りする同様の問題に遭遇しました。

これを解決するためのアプローチは、2 つのフレームワーク間に明示的なブリッジを作成することです。運が良ければ、非 CDI フレームワークにオブジェクト作成をカスタマイズできるフックがあり、そこに CDI を使用するものをプラグインできます (たとえば、Stripes Web フレームワークでは、ActionResolverCDI を使用する を作成できます)。

そうでない場合、ブリッジはプロキシの形式を取る必要があります。そのプロキシ内で、明示的な CDI ルックアップを実行できます。を取得することで、CDI にブートストラップBeanManagerできます。これにより、コンテキストを設定し、そこに 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 の使用が終了したら (このルックアップをリクエストごとに 1 回行うか、アプリケーションの存続期間ごとに 1 回行うかはあなた次第です)、次のように Bean を解放します。

creationalContext.release();
于 2013-08-29T10:36:03.520 に答える