目次||

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の実装にはそれぞれ独自の管理手法が実装されており、アクセスが許可されるかどうかの判断にさらに制限が追加されている可能性もあります。


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

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

アクセス制御の図

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

呼出しチェーンの中に、要求されたアクセス権を持たない呼出し元がある場合は、AccessControlExceptionがスローされます。ただし、ある呼出し元のドメインにそのアクセス権が与えられており、その呼出し元に「特権付き(privileged)」のマークが付けられていて(次のセクションを参照)、それ以後その呼出し元から(直接または間接的に)呼び出されるすべての関係者がそのアクセス権を持つ場合は例外です。2つの実装手法があります。 この方法の長所は、アクセスが許可されているかどうかのチェックが容易になり、多くの場合、迅速になるという点です。短所は、ドメインの境界を越えた呼出しの頻度よりもアクセス権のチェックの頻度の方がかなり少なくなるために、アクセス権の更新のかなりの部分が無駄な行為になる可能性があるという点です。 このアプローチの可能性のある短所の1つに、アクセス権のチェック時のパフォーマンスの低下があります。ただし、この低下は「積極的評価」アプローチでも発生する可能性があります(初期に、ドメイン間の各呼出しに拡散されますが)。私たちが行った実装では、良好なパフォーマンスが達成されました。全般的に、消極的評価の方法が経済的だと考えます。

したがって、アクセス権のチェックのアルゴリズムは、現在は「消極的評価」で実装されています。現在のスレッドが呼出し元1から呼出し元2を経て呼出し元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メソッドは、コンテキスト引数のないdoPrivilegedの呼出しによって「特権付き」のマークが付けられた呼出し側に届いたときに、チェックを中止します(コンテキスト引数については次のセクションを参照)。その呼出し元のドメインが指定されたアクセス権を持っている場合は、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は、Objectを返すrunという単一のメソッドを持つインタフェースです。上の例は、そのインタフェースを実装する匿名の内部クラス実装の作成を示しており、runメソッドの固定実装が提供されています。doPrivilegedへの呼出し時に、PrivilegedActionの実装のインスタンスが渡されます。doPrivilegedメソッドは、特権を有効にした後で、PrivilegedActionの実装からrunメソッドを呼び出し、runメソッドの戻り値をdoPrivilegedの戻り値として返します(これは、上の例では無視されています)。

(内部クラスの詳細は、Javaチュートリアルの「ネストされたクラス」を参照してください。

次に、値を返す必要がある場合の例を示します。

  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呼出しに至るまでの呼出しチェーン内のこれ以降のすべての呼出し元も同様になります。

コードを「特権付き」としてマーキングする方法の詳細は、「特権ブロックのためのAPI」を参照してください。


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から呼出し元2を経て呼出し元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 © 1993, 2020, Oracle and/or its affiliates. All rights reserved.