第3章 JBoss セキュリティモデル

他の JBoss アーキテクチャと同様に、最低限レベルのセキュリティは代替の実装が提供されるインターフェースのセットとして定義されます。次のインターフェースは JBoss サーバーセキュリティレイヤを定義します。
  • org.jboss.security.AuthenticationManager
  • org.jboss.security.RealmMapping
  • org.jboss.security.SecurityProxy
  • org.jboss.security.AuthorizationManager
  • org.jboss.security.AuditManager
  • org.jboss.security.MappingManager
図3.1「JBoss サーバー EJB コンテナ要素とのセキュリティモデルのインターフェース関係」 はセキュリティインターフェースと EJB コンテナアーキテクチャとの関係のクラスダイアグラムを示しています。
JBoss サーバー EJB コンテナ要素とのセキュリティモデルのインターフェース関係

図3.1 JBoss サーバー EJB コンテナ要素とのセキュリティモデルのインターフェース関係

EJB コンテナレイヤはクラス org.jboss.ejb.Containerorg.jboss.SecurityInterceptororg.jboss.SecurityProxyInterceptor で表されます。その他のクラスは JBoss セキュリティサブシステムにより提供されるインターフェースとクラスです。
J2EE セキュリティモデルの実装に必要な 2 つのインターフェースは以下のとおりです。
  • org.jboss.security.AuthenticationManager
  • org.jboss.security.AuthorizationManager
図3.1「JBoss サーバー EJB コンテナ要素とのセキュリティモデルのインターフェース関係」 で示されたセキュリティインターフェースのロールは以下のとおり要約されます。

セキュリティインターフェースのロール

AuthenticationManager
このインターフェースの役割は プリンシパル と関連付けられる資格情報を検証することです。Principal とはユーザー名、雇用者番号、ソーシャルセキュリティ番号などのアイデンティティです。資格情報 はパスワード、セッションキー、デジタル署名などアイデンティティの証明です。isValid メソッドは呼び出されて、ユーザーアイデンティティと動作環境で既知の関連付けられた資格情報がユーザーアイデンティティの有効な証明となるかを決定します。
AuthorizationManager
このインターフェースの役割は Java EE 仕様により指示されたアクセス制御です。このインターフェースの実装により、プラグ可能な承認に役立つ Policy Provider のセットをスタックできます。
SecurityProxy
このインターフェースはカスタムの SecurityProxyInterceptor プラグインの要件を記述します。SecurityProxy により EJB ホームとリモートインターフェースメソッドの両方に対してメソッドベースでカスタムセキュリティチェックの外部化が可能になります。
AuditManager
このインターフェースの役割はセキュリティイベントの監査証跡を提供することです。
MappingManager
このインターフェースの役割は Principal、Role、Attribute のマッピングを提供することです。AuthorizationManager の実装は内部的にマッピングマネージャを呼び出し、アクセス制御を実行する前にロールをマップすることができます。
SecurityDomain
これは AuthenticationManagerRealmMappingSubjectSecurityManager インターフェースの拡張です。SecurityDomain がコンポーネントでセキュリティを実装するための推奨される方法です。その理由として、JAAS Subject が持つ利点、ASP スタイルアプリケーションとリソースデプロイメントに提供される強化したサポートがあげられます。java.security.KeyStore、Java Secure Socket Extension (JSSE) com.sun.net.ssl.KeyManagerFactorycom.sun.net.ssl.TrustManagerFactory インターフェースはクラスに含まれます。
RealmMapping
このインターフェースの役割はプリンシパルマッピングとロールマッピングです。getPrincipal メソッドは動作環境でユーザーアイデンティティを既知として取り、アプリケーションドメインアイデンティティを返します。doesUserHaveRole メソッドは動作環境のユーザーアイデンティティがアプリケーションドメインから指定されたロールを割り当てられていることを検証します。
AuthenticationManagerRealmMappingSecurityProxy インターフェースは JAAS 関連クラスと関連がないことに注意してください。JBossSX フレームワークは JAAS に大きく依存していますが、Java EE セキュリティモデルの実装に必要な基本的なセキュリティインターフェースは依存していません。JBossSX フレームワークは JAAS に基づく基本的なセキュリティプラグインインターフェースの単なる実装です。
これについては 図3.2「JBossSX フレームワーク実装クラスと JBoss サーバー EJB コンテナレイヤ」 のコンポーネント図で示されています。このプラグインアーキテクチャでは、JAAS ベースの JBossSX 実装クラスを自由に非 JAAS カスタムセキュリティマネージャ実装に置き換えることができることを表しています。この方法については 図3.2「JBossSX フレームワーク実装クラスと JBoss サーバー EJB コンテナレイヤ」 の JBossSX 設定に使用できる JBossSX MBean を参照してください。
JBossSX フレームワーク実装クラスと JBoss サーバー EJB コンテナレイヤ

図3.2 JBossSX フレームワーク実装クラスと JBoss サーバー EJB コンテナレイヤ

3.1. 宣言型セキュリティの有効化の再確認

本項の冒頭で Java EE 標準セキュリティモデルに関する説明は、セキュリティを有効にする JBoss サーバー固有のデプロイメント記述子を使用する要件を説明して終わりました。この設定に関する詳細は 図3.3「jboss.xml と jboss-web.xml セキュリティ要素のサブセット」 に示されています。ここでは、JBoss 固有の EJB と Web アプリケーションのデプロイメント記述子のセキュリティ関連要素を示しています。
jboss.xml と jboss-web.xml セキュリティ要素のサブセット

図3.3 jboss.xml と jboss-web.xml セキュリティ要素のサブセット

<security-domain> 要素の値は、JBoss が EJB と Web コンテナに使用するセキュリティマネージャインターフェース実装の JNDI 名を指定します。これは AuthenticationManagerRealmMapping インターフェースの両方を実装するオブジェクトです。トップレベルの要素として指定すると、デプロイメントユニットのすべての EJB に対しどのセキュリティドメインを指定するかを定義します。デプロイメントユニット内で複数のセキュリティマネージャを混在させると、内部コンポーネントの動作や管理が複雑になるため、これが一般的な使用方法です。
個別の EJB にセキュリティドメインを指定するには、コンテナの設定レベルで <security-domain> を指定します。これによりすべてのトップレベルの <security-domain> 要素を上書きします。
<unauthenticated-principal> 要素は、認証されていないユーザーが EJB を呼び出す場合に EJBContext.getUserPrincipal メソッドにより返される Principal オブジェクトに使用する名前を指定します。これにより認証されていない呼び出し側に特別なパーミッションを伝えることはありません。主要な目的は、セキュアでないサーブレットと JSP ページがセキュアでない EJB を呼び出すことができ、目的の EJB が getUserPrincipal メソッドを使用して呼び出し側に対して nul でない Principal を取得できるようにすることです。これは J2EE 仕様要件です。
<security-proxy> 要素はカスタムのセキュリティプロキシ実装を特定します。これにより、セキュリティロジックを EJB 実装に組み込むことなく EJB 宣言型セキュリティモデルのスコープ外で要求ごとのセキュリティチェックが可能になります。org.jboss.security.SecurityProxy インターフェースの実装を使用できます。別の方法として、EJB のホーム、リモート、ローカルホーム、またはローカルインターフェースでメソッドを実装するオブジェクトを使用する共通のインターフェースを使用することもできます。特定のクラスが SecurityProxy インターフェースを実装しない場合は、インスタンスはメソッド呼び出しをオブジェクトに委譲する SecurityProxy 実装でラップされる必要があります。org.jboss.security.SubjectSecurityProxy はデフォルトの JBossSX インストールで使用される SecurityProxy 実装の一例です。
簡易ステートレスセッション Bean のコンテキストのカスタム SecurityProxy の簡単な例を見てみましょう。カスタムの SecurityProxy は、Bean の echo メソッドを 4 文字の単語を使用してその引数として呼び出す者がいないことを検証します。これはロールベースのセキュリティでは不可能な確認です。セキュリティコンテキストはメソッド引数であり、呼び出し側のプロパティではないため FourLetterEchoInvoker ロールを定義することはできません。カスタムの SecurityProxy のコードは 例3.1「カスタムの EchoSecurityProxy 実装」 を参照してください。

例3.1 カスタムの EchoSecurityProxy 実装

package org.jboss.book.security.ex1;
                
import java.lang.reflect.Method;
import javax.ejb.EJBContext;
                
import org.apache.log4j.Category;
                
import org.jboss.security.SecurityProxy;
                
/** A simple example of a custom SecurityProxy implementation
 *  that demonstrates method argument based security checks.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.4 $
 */
public class EchoSecurityProxy implements SecurityProxy
{
    Category log = Category.getInstance(EchoSecurityProxy.class);
    Method echo;
    
    public void init(Class beanHome, Class beanRemote,
                     Object securityMgr)
        throws InstantiationException
    {
        log.debug("init, beanHome="+beanHome
                  + ", beanRemote="+beanRemote
                  + ", securityMgr="+securityMgr);
        // Get the echo method for equality testing in invoke
        try {
            Class[] params = {String.class};
            echo = beanRemote.getDeclaredMethod("echo", params);
        } catch(Exception e) {
            String msg = "Failed to find an echo(String) method";
            log.error(msg, e);
            throw new InstantiationException(msg);
        }
    }
    
    public void setEJBContext(EJBContext ctx)
    {
        log.debug("setEJBContext, ctx="+ctx);
    }
    
    public void invokeHome(Method m, Object[] args)
        throws SecurityException
    {
        // We don't validate access to home methods
    }

    public void invoke(Method m, Object[] args, Object bean)
        throws SecurityException
    {
        log.debug("invoke, m="+m);
        // Check for the echo method
        if (m.equals(echo)) {
            // Validate that the msg arg is not 4 letter word
            String arg = (String) args[0];
            if (arg == null || arg.length() == 4)
                throw new SecurityException("No 4 letter words");
        }
        // We are not responsible for doing the invoke
    }
}           

EchoSecurityProxy は、Bean インスタンスで呼び出されるメソッドが init メソッドをロードした echo(String) メソッドと一致することを確認します。一致があると、メソッド引数は取得され、その長さが 4 または null に対して比較されます。どちらの場合でも SecurityException がスローされます。
これは確かに不自然な例ですが、不自然なのはそのアプリケーション内のみです。アプリケーションがメソッド引数の値に基づいてセキュリティチェックを実行する必要があることは一般的な要件です。この例のポイントは、標準宣言型セキュリティモデルの範囲を越えたカスタムのセキュリティが Bean の実装に依存せずに導入できる方法を示していることです。これによりセキュリティ要件の仕様とコーディングをセキュリティの専門家に委譲することが可能です。セキュリティプロキシレイヤは Bean 実装に依存せずに行うことが可能なため、セキュリティはデプロイメント環境の要件と一致するよう変更できます。
EchoBean に対するカスタムのプロキシとして EchoSecurityProxy をインストールする関連付けられた jboss.xml 記述子は 例3.2「jboss.xml 記述子」 で示されています。

例3.2 jboss.xml 記述子

<jboss>
    <security-domain>java:/jaas/other</security-domain>
                
    <enterprise-beans>
        <session>
            <ejb-name>EchoBean</ejb-name>
            <security-proxy>org.jboss.book.security.ex1.EchoSecurityProxy</security-proxy>
        </session>
    </enterprise-beans>
</jboss>
さて、この一部で図解されたように引数 HelloFour を付けて EchoBean.echo メソッドを呼び出そうとするクライアントを実行して、カスタムのプロキシをテストしてみましょう。
public class ExClient
{
    public static void main(String args[])
        throws Exception
    {
        Logger log = Logger.getLogger("ExClient");
        log.info("Looking up EchoBean");

        InitialContext iniCtx = new InitialContext();
        Object ref = iniCtx.lookup("EchoBean");
        EchoHome home = (EchoHome) ref;
        Echo echo = home.create();

        log.info("Created Echo");
        log.info("Echo.echo('Hello') = "+echo.echo("Hello"));
        log.info("Echo.echo('Four') = "+echo.echo("Four"));
    }
}
最初の呼び出しは成功するはずですが、2 番目の呼び出しは、Four が 4 文字の単語であるため失敗するはずです。examples ディレクトリから Ant を使用して次のようにクライアントを実行します。
[examples]$ ant -Dchap=security -Dex=1 run-example
run-example1:
...
     [echo] Waiting for 5 seconds for deploy...
     [java] [INFO,ExClient] Looking up EchoBean
     [java] [INFO,ExClient] Created Echo
     [java] [INFO,ExClient] Echo.echo('Hello') = Hello
     [java] Exception in thread "main" java.rmi.AccessException: SecurityException; nested exception is: 
     [java]     java.lang.SecurityException: No 4 letter words
...
     [java] Caused by: java.lang.SecurityException: No 4 letter words
...
予想通り echo('Hello') メソッド呼び出しは成功し、echo('Four') メソッド呼び出しは乱雑な例外という結果となりました。上記の出力は本書では紙面の都合上省略されています。例外で重要な箇所は、EchoSecurityProxy により生成された SecurityException("No 4 letter words") が希望通り試行されたメソッド呼び出しを中止するために投げられたということです。