2013-08-29 8 views
11

私が開発しているアプリケーションでは、JPA/Hibernate、JSF、CDI、およびEJBとともに、まっすぐなJava 6 EEとJBoss(春などは使用しない)を使用しています。カスタムShiroでCDIマネージドBeanを注入するAuthorizeRealm

私は多くの良い一般的なセキュリティソリューション(推奨は歓迎です)が見つかりませんでしたが、私が見つけた最も良い賭けはApache Shiroです。

しかし、これには多くの欠点があるようです。そのうちのいくつかは、あなたはBalus C'sサイトで約読むことができます:

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

しかし、私はすでに依存性注入とプロキシに関するhereを述べているもう一つの大きな問題につまずいてきました。

基本的には、認証に必要なすべての機能を備えたJPAベースのUserDAOがあります。私のデータベースは、persistence.xmlとmydatabase-ds.xml(JBoss用)できれいに設定されています。

この設定情報をすべて二度と複製してユーザテーブルクエリをshiro.iniに追加するのは馬鹿馬鹿しいようです。だから、私はJdbcRealmを使うのではなく、自分のRealmを書くことを選んだのです。この時

私の最初の試みは、AuthorizingRealmをサブクラス化することだった...のようなもの:親クラスの最後のinit()メソッドがあるのでMyAppRealmがプロキシすることができませんので

@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に必要なすべてのインターフェイスを実装し、それをAuthorizingRealmのインスタンスに委譲するという2番目の試みでした。私はこれが好きではありませんでしたが、試してみるかもしれません。

これはさらに私を得る、webappが起動しますが、まだ不足しています。その理由は、私は私のレルムのクラスを指定し、shiro.ini、設定ファイルである:

myAppRealm = com.myapp.MyAppRealm 

これはかなり史郎がMyAppRealmインスタンスを作成するための責任を負うことを私に伝えます。したがって、CDIは管理されず、注入もされません。これはまさに私が見ているものです。

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

私はこれをどのように回避することができますか?

答えて

8

これは古典的な問題です:オブジェクトライフサイクルを管理する2つの異なるフレームワークがあり、それらを相互作用させる必要がありますが、両方とも完全な制御を要求します(私の精神的なイメージはGodzillaとGameraです東京都心と戦う)。 ShiroはCDIの競合相手だとはすぐに考えないかもしれませんが、そのオブジェクトのインスタンスを作成するため、基本的に小さな基本的な依存性注入フレームワーク(おそらくGreenspun's tenth ruleのDIバージョン)が含まれています。私は同様の問題に遭遇し、Webフレームワークを作成しました。Webフレームワークは、バッキングBeanのインスタンスを作成して挿入し、CDIとやりとりします。

これを解決するアプローチは、2つのフレームワークの間に明示的なブリッジを作成することです。あなたが本当に運が良ければ、非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は、あなたが今creationalContextとして保持するコンテキストにバインド注入し、CDI-マネージドBeanです。

あなたが豆を終了している(一度リクエストごとまたは一度アプリケーションの有効期間ごとに、この検索を行う場合、それはあなた次第です)、と豆をリリース:

creationalContext.release(); 
+0

ありがとうございます。これは非常に良い理論的な答えであり、この点に関しては頭の爪に当たります。私はbeanmanagerを使って '橋'をうまく構築したと思う。それはまったく可愛いわけではありませんが、私はそれを進化させて時間をかけて洗練させたいと考えています。 – lostdorje

8

あなたは初期化することによってこれを行うことができますアプリケーションの起動ライフサイクルの一部としてあなたの領域を取得して、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の内部の仕組みを活用しなくても、豆を管理していました。この理由は、CDIおよびEJBフレームワークの初期化後のWebレイヤーが表示されるまで 'shiro.ini'がロードされないためです。

+0

と、ファクトリによって提供される領域をセキュリティマネージャにどのように割り当てるのですか? – billdoor

+0

上記の 'shiro.ini'エントリがそれを処理します。 「org.apache.shiro.config.IniSecurityManagerFactory」のソースコードを見て、Shiroのしくみを見てみてください。 –

関連する問題