目次 | 前の項目 | 次の項目 | Java セキュリティアーキテクチャ |
ProtectionDomain クラスは、ドメインの特性をカプセル化します。 ドメインは、指定されたプリンシパルのセットのアクセス権が実行されているとき、アクセス権を与えられているインスタンスのあるクラスのセットを囲みます。ProtectionDomain は、CodeSource、ClassLoader、プリンシパルの配列、およびアクセス権のコレクションで構成されています。 CodeSource は、このドメインのすべてのクラスに対するコードベース (java.net.URL)、このドメインのすべてのコードに署名された秘密鍵に対応する公開鍵の証明書のセット (java.security.cert.Certificate タイプ) をカプセル化します。 プリンシパルは、コードの実行対象のユーザを表します。
ProtectionDomain の構築時に渡されるアクセス権は、適用されているポリシーにかかわらず、ドメインにバインドされているアクセス権の静的なセットを表します。 その後、セキュリティチェック時に ProtectionDomain によって、現在のポリシーへの問い合わせが行われ、ドメインに付与されている動的なアクセス権が取得されます。
異なる CodeSource からのクラス、または異なるプリンシパルに実行されるクラスは、別個のドメインに属しています。
現在 Java 2 SDK の一部として提供されているコードは、すべてシステムコードとみなされ、ただ 1 つのシステムドメイン内で動作します。 アプレットまたはアプリケーションは、それぞれのポリシーによって決められるドメイン内で動作します。
システムドメイン以外のドメイン内のオブジェクトが、システムドメイン以外の別のドメイン内のオブジェクトを自動的に検出できないようにすることは可能です。 この区分けは、クラスを注意深く解決し、注意深くローディングすることにより行います。 たとえば、ドメインごとに異なるクラスローダを使用するなどの方法です。 ただし、この方法では SecureClassLoader (またはそのサブクラス) が異なるドメインからクラスをロードできるので、これらのクラスが同じ名前空間に共存することが許されます (クラスローダによる区分けの場合と同様)。
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; };
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.html
とhttp://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
を参照してください。
スレッドが新しいスレッドを生成するときは、新しいスタックが生成されます。 新しいスレッドが生成されたときに現在のセキュリティコンテキストが書き換えられない場合、新しいスレッドの中で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 の公の変更はありません。
前述したように、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);