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