目次 | 前の項目 | 次の項目 Java セキュリティーアーキテクチャー


4 アクセス制御機構とアルゴリズム


4.1 java.security.ProtectionDomain

ProtectionDomain クラスは、ドメインの特性をカプセル化します。ドメインは、指定された主体のセットのアクセス権が実行されているとき、アクセス権を与えられているインスタンスのあるクラスのセットを囲みます。

ProtectionDomain は、CodeSource、ClassLoader、主体の配列、およびアクセス権のコレクションで構成されています。CodeSource は、このドメインのすべてのクラスに対するコードベース(java.net.URL)、このドメインのすべてのコードに署名された秘密鍵に対応する公開鍵の証明書のセット (java.security.cert.Certificate タイプ) をカプセル化します。主体は、コードの実行対象のユーザーを表します。

ProtectionDomain の構築時に渡されるアクセス権は、適用されているポリシーにかかわらず、ドメインにバインドされているアクセス権の静的なセットを表します。その後、セキュリティーチェック時に ProtectionDomain によって、現在のポリシーへの問い合わせが行われ、ドメインに付与されている動的なアクセス権が取得されます。

異なる CodeSource からのクラス、または異なる主体に実行されるクラスは、別個のドメインに属しています。

現在 Java 2 SDK の一部として提供されているコードは、すべてシステムコードとみなされ、ただ 1 つのシステムドメイン内で動作します。アプレットまたはアプリケーションは、それぞれのポリシーによって決められるドメイン内で動作します。

システムドメイン以外のドメイン内のオブジェクトが、システムドメイン以外の別のドメイン内のオブジェクトを自動的に検出できないようにすることは可能です。この区分けは、クラスを注意深く解決し、注意深くローディングすることにより行います。 たとえば、ドメインごとに異なるクラスローダを使用するなどの方法です。ただし、この方法では SecureClassLoader (またはそのサブクラス) が異なるドメインからクラスをロードできるので、これらのクラスが同じ名前空間に共存することが許されます (クラスローダによる区分けの場合と同様)。


4.2 java.security.AccessController

AccessController クラスは、次の 3 つの目的に使用されます。 それぞれの目的については、このあとで詳しく説明します。

システム資源へのアクセスを制御するコードは、特定のセキュリティーモデルを使用する場合は AccessController メソッドおよびメソッドが利用するアクセス制御アルゴリズムを呼び出します。一方、アプリケーションがセキュリティーモデルを、実行時にインストールされる SecurityManager にゆだねる場合は、このメソッドではなく SecurityManager クラスのメソッドを呼び出します。

たとえば、アクセス制御の呼び出しには通常、次のようなコードを使用していました (JDK の旧バージョンより)。

 ClassLoader loader = this.getClass().getClassLoader();
        if (loader != null) {
                SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkRead("path/file");
            }
        }
新しいアーキテクチャーでは、呼び出し元のクラスに関連するクラスローダの有無にかかわらず、チェックを呼び出すのが一般的な方法です。コードは、次のような簡潔なものになります。

FilePermission perm = new FilePermission("path/file", "read");
AccessController.checkPermission(perm);
AccessController の checkPermission メソッドは、現在の実行コンテキストを調べて、要求されたアクセスが許可されているかどうかを正しく判断します。アクセスが許可されている場合は、ただちに復帰します。アクセスが許可されていない場合は、AccessControlException (java.lang.SecurityException のサブクラス) 例外が発行されます。

ブラウザによっては、次のようなケースがあるので注意が必要です。 従来のブラウザの中には、インストールされている SecurityManager が異なるセキュリティー状態を示すものがあり、その結果、異なる動作が発生する可能性があります。旧バージョンとの互換性のため、SecurityManager の checkPermission メソッドを使用できます。

SecurityManager security = System.getSecurityManager();
if (security != null) {
    FilePermission perm = new FilePermission("path/file", "read");
    security.checkPermission(perm);
}
現状では、SecurityManager のこの使用法を変更しませんが、将来、Java 2 SDK に適切なアクセス制御アルゴリズムが組み込まれたときには、以後のアプリケーションのプログラミングには新しい技法を使うことをお勧めします。

SecurityManager の checkPermission メソッドは、デフォルトでは実際に AccessController の checkPermission メソッドを呼び出します。SecurityManager の実装にはそれぞれ独自の管理手法が実装されており、アクセスが許可されるかどうかの判断にさらに制限が追加されている可能性もあります。


アクセス権チェックのアルゴリズム

次の図のように、複数の呼び出し元のチェーンを持つスレッドで、アクセス制御のチェックを行うとします (保護ドメインの境界を越える、複数メソッドの呼び出しと仮定)。

アクセス制御の図

AccessController の checkPermission メソッドが一番新しい呼び出し元 (File クラスのメソッドなど) によって呼び出されたとき、要求されたアクセスが許可されているかどうかを判断する基本のアルゴリズムは次のとおりです。

呼び出しチェーンの中に、要求されたアクセス権を持たない呼び出し元がある場合は、AccessControlException が発行されます。 ただし、ある呼び出し元のドメインにそのアクセス権が与えられており、その呼び出し元に「特権付き (privileged)」のマークが付けられていて (次のセクションを参照)、それ以後その呼び出し元から (直接または間接的に) 呼び出されるすべての関係者がそのアクセス権を持つ場合は例外です。
2 つの実装手法があります。

この方法の長所は、アクセスが許可されているかどうかのチェックが容易になり、多くの場合、迅速になるという点です。短所は、ドメインの境界を越えた呼び出しの頻度よりもアクセス権のチェックの頻度の方がかなり少なくなるために、アクセス権の更新のかなりの部分が無駄な行為になる可能性があるという点です。

この方法の短所の 1 つに、アクセス権のチェック時にパフォーマンスが低下する可能性があります。 これは「積極的評価」の場合でも起こる可能性がありますが、早い時期に起こり、ドメインを越えた呼び出しの 1 つずつに拡散しています。私たちが行なった実装では、良好なパフォーマンスが達成されました。 全般的に、消極的評価の方法が経済的だと考えます。

したがって、アクセス権のチェックのアルゴリズムは、現在は「消極的評価」の方法で実装されています。現在のスレッドが呼び出し元 1 から数字の昇順で m 個の呼び出し元を通過し、呼び出し元 m が checkPermission メソッドを呼び出すとします。基本アルゴリズム checkPermission は、アクセスが許可されているかどうかを判断するために次のコードを使用します (詳細については後述)。

 i = m;
 while (i > 0) {
     if (caller i's domain does not have the permission)
         throw AccessControlException
     else if (caller i is marked as privileged) 
         return;
     i = i - 1;
 };

4.2.2 特権の扱い

AccessController クラスの新しい static メソッドにより、クラスインスタンス内のコードは AccessController にそのコードの本体が「特権付きである」ことを通知できます。 特権付きの場合、そのコードの本体は、アクセスの要求を引き起こしたコードが何であるかにはかかわらず、利用可能な資源へのアクセスを要求する責任だけを負います。

つまり、ある呼び出し元がコンテキスト引数 (コンテキスト引数については後述) なしで doPrivileged メソッドを呼び出すと、その呼び出し元には「特権付き」のマークが付けられます。checkPermission メソッドは、アクセス制御の判断を行うときに「特権付き」のマーク付きの呼び出し元に出会うとチェックを中止します。その呼び出し元のドメインが指定されたアクセス権を持っている場合は、checkPermission はそれ以上のチェックを行わずに正常復帰し、要求されたアクセスが許可されていることを示します。そのドメインが指定されたアクセス権を持たない場合は、通常は例外が発行されます。

「特権付き」機能の通常の使用法は、次のとおりです。

「特権付き」ブロックからの戻り値が必要ない場合は、次のようにします。

  somemethod() {
       ...normal code here...
       AccessController.doPrivileged(new PrivilegedAction() {
           public Object run() {
               // privileged code goes here, for example:
               System.loadLibrary("awt");
               return null; // nothing to return
           }
       });
       ...normal code here...
  }
PrivilegedAction は、run という単一のメソッドを持つインタフェースで、run は Object を返します。上の例は、そのインタフェースを実装する匿名の内部クラス実装を示しており、run メソッドの固定実装が提供されています。doPrivileged への呼び出し時に、PrivilegedAction の実装のインスタンスが渡されます。doPrivileged メソッドは、特権を有効にしたあとで PrivilegedAction の実行から run メソッドを呼び出し、run メソッドの戻り値を doPrivileged の戻り値として返します。 ただし、この例では戻り値は無視されます。

内部クラスの詳細については、http://java.sun.com/products/jdk/1.1/docs/guide/innerclasses/spec/innerclasses.doc.html の「Inner Classes Specification」または http://java.sun.com/docs/books/tutorial/java/more/nested.htmlhttp://java.sun.com/docs/books/tutorial/java/more/innerclasses.html の「More Features of the Java Language trail of the Java Tutorial」のページを参照してください。

戻り値を返す必要がある場合は、次のようにします。

  somemethod() {
       ...normal code here...
       String user = (String) AccessController.doPrivileged(
         new PrivilegedAction() {
           public Object run() {
               return System.getProperty("user.name");
           }
         }
       );
       ...normal code here...
  }
run メソッド内の動作で「チェック終了」例外 (メソッドの throws 節の中に記述される) を発行する場合は、PrivilegedAction インタフェースではなく PrivilegedExceptionAction インタフェースを使う必要があります。

  somemethod() throws FileNotFoundException {
       ...normal code here...
     try {
       FileInputStream fis = (FileInputStream)
        AccessController.doPrivileged(
         new PrivilegedExceptionAction() {
           public Object run() throws FileNotFoundException {
               return new FileInputStream("someFile");
           }
         }
       );
     } catch (PrivilegedActionException e) {
       // e.getException() should be an instance of
       // FileNotFoundException,
       // as only "checked" exceptions will be "wrapped" in a
       // <code>PrivilegedActionException</code>.
       throw (FileNotFoundException) e.getException();
     }
       ...normal code here...
 }
「特権付き」にはいくつか重要な点があります。まず、この概念は単一スレッドの内部でだけ存在します。特権付きのコードが終了すると、その特権はただちに消滅します。

次に、この例では、run メソッド内のコード本体は特権付きです。しかし、このコードが特権を持たない信頼性の低いコードを呼び出すと、呼び出されたコードは結果的にすべての特権を与えられることになります。 アクセス権は、特権付きのコードがそのアクセス権を持つ場合にだけ与えられ、checkPermission の呼び出しに至る呼び出しチェーンのこれ以降の呼び出し元もすべて同様になります。

「特権付き」のコードを作成する方法の詳細については、http://java.sun.com/j2se/sdk/1.2/docs/guide/security/doprivileged.html を参照してください。


4.3 アクセス制御コンテキストの継承

スレッドが新しいスレッドを生成するときは、新しいスタックが生成されます。新しいスレッドが生成されたときに現在のセキュリティーコンテキストが書き換えられない場合、新しいスレッドの中で AccessController.checkPermission が呼び出されたときのセキュリティーの判断は、新しいスレッドのコンテキストだけに基づいて行われ、親スレッドのコンテキストは考慮されません。

この明確なスタックの考え方そのものはセキュリティー上の問題にはなりませんが、安全なコード (特にシステムコード) を記述する上で、隠れた間違いを起こしやすくなります。経験の浅い開発者は、子スレッド (信頼できないコードを含まないものなど) は、親スレッド (信頼できないコードを含むものなど) から同じセキュリティーコンテキストを継承すると仮定してしまうことが考えられます。これは、親のコンテキストが実際は保存されていない場合、制御対象の資源に新しいスレッドからアクセスすると (その資源を信頼できないコードに渡すと) 予期しないセキュリティーホールの原因となります。

このため、新しいスレッドが生成されるときは、子スレッドの生成時点における親スレッドのセキュリティーコンテキストを子スレッドが確実に自動継承 (スレッドの生成などのコードを通じて) するようにし、それ以降の子スレッドでの checkPermission の呼び出しで、継承した親のコンテキストが考慮されるようにしました。

つまり、論理的スレッドコンテキストは、親のコンテキスト (後述の AccessControlContext の形式) と現在のコンテキストの両方を含むように拡張され、アクセス権のチェックのためのアルゴリズムは、次のように拡張されます。前述したように、checkPermission の呼び出しに至るまでには m 個の呼び出し元があります。 AccessControlContext の checkPermission メソッドの詳細については、あとで説明します。

 i = m;
 while (i > 0) {
     if (caller i's domain does not have the permission)
         throw AccessControlException
     else if (caller i is marked as privileged) 
         return;
     i = i - 1;
 };

 // Next, check the context inherited when
 // the thread was created. Whenever a new thread is created, the
 // AccessControlContext at that time is
 // stored and associated with the new thread, as the "inherited"
 // context.

 inheritedContext.checkPermission(permission);
この継承は、たとえば、孫が親とその親の両方の性質を継承するのと同じように過渡的なものです。また、継承されたコンテキストは、子コンテキストが最初に実行される時点ではなく、子コンテキストが生成された時点で保存されます。継承の機能については API の公の変更はありません。


4.4 java.security.AccessControlContext

前述したように、AccessController の checkPermission メソッドは、現在の実行スレッド内で (継承したコンテキストも含む) セキュリティーチェックを行います。このようなセキュリティーチェックが別のコンテキスト内でだけ可能な場合には、問題が生じます。つまり、あるコンテキスト内で行うべきセキュリティーチェックを、実際には別のコンテキストで行うことが必要な場合があります。たとえば、あるスレッドが別のスレッドにイベントを送ったとき、そのサービスにはコントローラ資源へのアクセスが必要であるのに、要求イベントを処理する後者のスレッドがアクセス制御を行うための適切なコンテキストを持っていないような場合です。

この問題に対処するために、AccessController に getContextメソッドと AccessControlContext クラスが用意されました。getContext メソッドは、現在の呼び出しコンテキストを AccessControlContext オブジェクトに格納して返します。呼び出しの例を次に示します。

AccessControlContext acc = AccessController.getContext();
このコンテキストは、適切な情報を取り込み、別のコンテキストからこのコンテキスト情報を調べることにより、アクセス制御の判断を行えるようにします。たとえば、あるスレッドは別のスレッドに要求イベントを送りながら、このコンテキスト情報を提供することができます。AccessControlContext そのものには checkPermission メソッドがあり、現在の実行スレッドのコンテキストではなく保持しているコンテキストに基づいてアクセスの判断を行います。したがって、2 つ目のスレッドは必要な場合に次の呼び出しを行うことにより、適切なセキュリティーチェックができます。

acc.checkPermission(permission);
このメソッドの呼び出しは 2 つ目のスレッドで行われますが、1 つ目のスレッドのコンテキストでセキュリティーチェックを行うことと同じことです。

また、あるアクセス制御コンテキストに対して 1 つまたは複数のアクセス権のチェックが必要なときに、どのアクセス権をチェックするかの優先度が不明なことがあります。この場合は、次のコンテキストで doPrivileged メソッドを使用できます。

  somemethod() {
        AccessController.doPrivileged(new PrivilegedAction() {
             public Object run() {
                // Code goes here. Any permission checks from 
                // this point forward require both the current 
                // context and the snapshot's context to have 
                // the desired permission.
             }
        }, acc);
        ...normal code here...
AccessController の checkPermission メソッドが利用するアルゴリズムは、これですべてです。現在のスレッドが呼び出し元 1 から数字の昇順で m 個の呼び出し元を通過し、呼び出し元 m が checkPermission メソッドを呼び出すとします。アルゴリズム checkPermission は、アクセスが許可されているかどうかを判断するために次のコードを使用します。

  i = m;
  while (i > 0) {
     if (caller i's domain does not have the permission)
        throw AccessControlException
     else if (caller i is marked as privileged) {
        if (a context was specified in the call to doPrivileged)
           context.checkPermission(permission);
        return;
     }
     i = i - 1;
  };


  // Next, check the context inherited when
  // the thread was created. Whenever a new thread is created, the
  // AccessControlContext at that time is
  // stored and associated with the new thread, as the "inherited"
  // context.

  inheritedContext.checkPermission(permission);


目次 | 前の項目 | 次の項目
Copyright © 1997-1999 Sun Microsystems, Inc. All Rights Reserved.