このドキュメントでは、特権コードの意味とその使用目的について説明した後、APIの使用方法を示します。次のトピックについて説明します。
JDKのインストール・ポリシーにより、特定のコード・ソースに基づくコードに対して許可されるアクセス権、つまりシステム・リソースに対するアクセスの種類が決まります。CodeSource
型のコード・ソースは、基本的に、コードの場所(URL)と、コードへの署名に使われている非公開鍵に対応する公開鍵を含む証明書の参照(コードが署名されている場合)で、構成されています。
ポリシーは、Policy
オブジェクトによって表されます。具体的には、Policy
クラス(java.security
パッケージ内)で定義されているabstractメソッドを実装したPolicy
サブクラスによって表されます。
Policy
オブジェクトが使用するポリシー情報がどこに置かれるかは、Policy
の実装によります。Policy
のリファレンス実装では、ポリシー情報をポリシー構成ファイルから得ます。デフォルトのPolicy
のリファレンス実装とそこで読み取られるポリシー・ファイルで使用する構文については、「Policyのデフォルト実装とポリシー・ファイルの構文」を参照してください。Policy Toolを使った(必須の構文を知る必要のない)ポリシー・ファイルの作成方法の詳細は、Policy Toolのドキュメント(Unix用、Windows用)を参照してください。
そのとき有効になっているセキュリティ・ポリシーの決定に従って、CodeSource
インスタンスと、そのCodeSource
に基づくコードに対して付与されているアクセス権は、保護ドメインに包含されます。したがって、同じ鍵で署名された同じURLにあるクラスは一般に同じドメインに置かれ、クラスは1つの保護ドメインのみに所属します。(ただし、同じ鍵で署名され、同じURLにあっても、別のクラス・ローダー・インスタンスでロードされたクラスは、一般に別のドメインに置かれます。)同じアクセス権を与えられていても、別のコード・ソースが基になっているクラスは、別のドメインに属します。
現在、JDKとともに提供されるすべてのクラスには、ロード時にすべてのアクセス権が与えられます(これは将来のリリースで変更される可能性があります)。これらのクラスのほとんどは、固有のシステム・ドメインに置かれます。さらに、拡張クラス・ローダーは$JAVA_HOME/jre/lib/ext
ディレクトリに含まれているJARファイルのコードを(これらのJARファイルのコードに固有のURLがあるため)別々のドメインにロードしますが、これらのドメインはJDKとともに提供されるクラスのために予約された固有のシステム・ドメインとは独立しています。
アプレットまたはアプリケーションは、それぞれのコード・ソースによって決められるドメイン内で動作します。アプレット(またはセキュリティ・マネージャの下で動作しているアプリケーション)がセキュリティ保護された操作(ファイルの読み書きなど)を行うためには、その操作を行うためのアクセス権がアプレットまたはアプリケーションに与えられている必要があります。
より具体的には、リソースにアクセスしようとする場合は常に、その地点に至るまでに実行スレッドがたどってきたすべてのコードが、そのリソース・アクセスのためのアクセス権を持っている必要があります。ただし、スレッドの中に特権付きのマークが付けられたコードがある場合は除きます。つまり、複数の呼出し側のチェーンが存在する実行のスレッドで、アクセス制御のチェックが行われる場合を考えます。(保護ドメインの境界を横断する可能性のある複数のメソッドの呼出しのような場合です。)最後の呼出し側がAccessController
のcheckPermission
メソッドを呼び出すとき、要求されたアクセスの許可または拒否を決定する基本的なアルゴリズムは、次のようになります。呼出しチェーンのいずれかの呼出し側のコードに、要求されたアクセス権がない場合、AccessControlException
がスローされます。ただし、このアクセス権を与えられているコードの呼出し側に特権付きのマークが付けられていて、この呼出し側からその後(直接または間接的に)呼び出されるすべての部分がこのアクセス権を持っている場合、この例外はスローされません。
AccessController.checkPermission
メソッドは、通常、check
という語で始まる特定のSecurityManager
メソッド(checkConnect
など)の呼出しによって、またはSecurityManager.checkPermission
メソッドによって間接的に呼び出されます。通常、これらのチェックはSecurityManager
がインストールされている場合にのみ行われます。AccessController.checkPermission
メソッドによってチェックされるコードは、最初にSystem.getSecurityManager
メソッドがnullを返すかどうかをチェックします。
コードを特権付きとしてマークすると、信頼できるコードは、それを呼び出しているコードが直接利用できるものより多くのリソースに、一時的にアクセスできるようになります。これは、次のような場合に必要になります。たとえば、あるアプリケーションがフォントが入ったファイルへの直接アクセスを許可されていないときに、ドキュメントを表示するシステム・ユーティリティがユーザーに代わってそのフォントを取得する必要がある場合などです。このシステム・ユーティリティは、フォントを取得するために特権付きになる必要があります。
以降のセクションでは、特権機能の使用方法について説明します。
privilegedブロック内から値を返す必要がない場合、doPrivileged
の呼出しは次の例のようになります。この例では、PrivilegedAction
インタフェースを実装するクラス、匿名クラスおよびラムダ式の3つの方法で特権コードを指定しています。
import java.security.*; public class NoReturnNoException { class MyAction implements PrivilegedAction<Void> { public Void run() { // Privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } } public void somemethod() { MyAction mya = new MyAction(); // Become privileged: AccessController.doPrivileged(mya); // Anonymous class AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // Privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } }); // Lambda expression AccessController.doPrivileged((PrivilegedAction<Void>) () -> { // Privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } ); } public static void main(String... args) { NoReturnNoException myApplication = new NoReturnNoException(); myApplication.somemethod(); } }
AccessController.doPrivileged
メソッドは、java.security.PrivilegedAction
型のオブジェクトを受け取り、そのrun
メソッドを特権モードで呼び出します。非同期の例外によりdoPrivileged
の実行が中断される場合でも、run
メソッドを実行したあとの特権の解除は、実装が保証します。ラムダ式を使ってdoPrivileged
を呼び出す場合は、ラムダ式をPrivilegedAction<Void>
型として明示的にキャストします。doPrivileged
メソッドには、PrivilegedExceptionAction
型のオブジェクトを取るバージョンも存在します。このバージョンの詳細は、「例外の処理」を参照してください。
PrivilegedAction
は、型パラメータで指定された型の値を返すrun
という名前の単一抽象メソッドを持つ関数型インタフェースです。
この例では、run
メソッドの戻り値は無視されています。特権コードを構成する実際の要素によっては、内部クラスの処理のために若干の変更が必要になる場合があります。たとえば、特権コードが例外をスローしたりローカル変数にアクセスしたりする場合は、後で説明する変更が必要になります。
特権の構造は十分に注意して使い、特権コードのセクションをできるかぎり小さくするよう常に心がける必要があります。つまり、run
メソッドの中のコードは、特権付きで実行する必要のあるコードのみに制限し、より一般的な処理はrun
メソッドの外側で行うようにします。また、doPrivileged
の呼出しは、特権を有効にする必要があるコードの中で行うようにします。それ自体がdoPrivileged
を呼び出すようなユーティリティ・クラスは、セキュリティ・ホールとなる危険があるため、作成しないでください。前の例で示したように、直接呼び出さなくても、PrivilegedAction
クラスのためのユーティリティ・クラスを記述できます。詳細は、「Javaプログラミング言語のセキュア・コーディング・ガイドライン」の「ガイドライン9-3: java.security.AccessController.doPrivileged
の安全な呼出し」を参照してください。
次に、値を返す必要がある場合の例を示します。
System.out.println( AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("user.name") ) );
ラムダ式または匿名の内部クラスを使う場合は、アクセスするローカル変数がすべてfinal
または事実上のfinalである必要があります。たとえば、
String lib = "awt"; AccessController.doPrivileged((PrivilegedAction<Void>) () -> { System.loadLibrary(lib); return null; // nothing to return } ); AccessController.doPrivileged(new PrivilegedAction<Void>() { public Object run() { System.loadLibrary(lib); return null; // nothing to return } });
変数lib
は、その値が変更されていないため、事実上のfinalです。たとえば、変数lib
の宣言の後に次の代入文を追加するとします。
lib = "swing";
コンパイラは、ラムダ式と匿名クラスの両方にあるSystem.loadLibrary
の呼出しを検出したときに、次のエラーを生成します。
error: local variables referenced from a lambda expression must be final or effectively final
error: local variables referenced from an inner class must be final or effectively final
詳細は、「ローカル・クラス」の「包含クラスのメンバーへのアクセス」を参照してください。
繰返し設定されるなどの理由で、既存の変数を事実上のfinalとして宣言できない場合は、doPrivileged
メソッドを呼び出す直前に新しいfinal
変数を作成し、その変数を他方の変数と等しいものとして設定します。たとえば、
String lib; // The lib variable gets set multiple times so you can't make it // effectively final. // Create a final String that you can use inside of the run method final String fLib = lib; AccessController.doPrivileged((PrivilegedAction<Void>) () -> { System.loadLibrary(fLib); return null; // nothing to return } );
run
メソッド内で実行された処理によってチェック例外(メソッドのthrows
節にリストする必要がある例外)がスローされた場合は、PrivilegedAction
インタフェースではなく、PrivilegedExceptionAction
インタフェースを使う必要があります。
public void processSomefile() throws IOException { try { Path path = FileSystems.getDefault().getPath("somefile"); BufferedReader br = AccessController.doPrivileged( (PrivilegedExceptionAction<BufferedReader>) () -> Files.newBufferedReader(path) ); // ... read from file and do something } catch (PrivilegedActionException e) { // e.getException() should be an instance of IOException // as only checked exceptions will be wrapped in a // PrivilegedActionException. throw (IOException) e.getException(); } }
チェック例外がrun
メソッドの実行中にスローされる場合は、この例で示したようにPrivilegedActionException
のラッパー例外に置いてからスローし、記述したコードを使ってキャッチします。
JDK 8では、他のアクセス権をチェックするためのスタックの完全なトラバースを禁止せずにコード内でコードの特権のサブセットを表明できるAccessController.doPrivileged
のバリアントを使用できます。このdoPrivileged
のバリアントには3つのパラメータがあり、そのうちの1つはこの特権のサブセットを指定するために使用します。たとえば、次のコード例はシステム・プロパティを取得するための特権を表明しています。
// Returns the value of the specified property. All code // is allowed to read the app.version and app.vendor // properties. public String getProperty(final String prop) { return AccessController.doPrivileged( (PrivilegedAction<String>) () -> System.getProperty(prop), null, new java.util.PropertyPermission("app.version", "read"), new java.util.PropertyPermission("app.vendor", "read") ); }
このバージョンのdoPrivileged
の1つ目のパラメータは、java.security.PrivilegedAction
型です。この例では、1つ目のパラメータは関数型インタフェースPrivilegedAction
を実装するラムダ式です。このインタフェースのrun
メソッドは、パラメータprop
で指定されたシステム・プロパティの値を返します。
このバージョンのdoPrivileged
の2つ目のパラメータは、AccessControlContext
型です。場合によっては、異なるコンテキスト(ワーカー・スレッドなど)で追加のセキュリティ・チェックを実行する必要があります。AccessControlContext.getContext
メソッドを使用して、特定の呼出しコンテキストからAccessControlContext
インスタンスを取得できます。このパラメータに(この例のように)null
を指定すると、doPrivileged
の呼出し時に追加のセキュリティ・チェックが実行されません。
このバージョンのdoPrivileged
の3つ目のパラメータは、可変引数パラメータであるPermission...
型です。つまり、1つ以上のPermission
パラメータを指定するか、またはPermission[]
のようにしてPermission
オブジェクトの配列を指定できます。この例では、doPrivileged
を呼び出すことでapp.version
プロパティとapp.vendor
プロパティを取得できます。
このdoPrivileged
の3つのパラメータ・バリアントは、最小特権のモードまたは追加特権のモードで使用できます。
doPrivileged
メソッドの典型的な使用例は、現在のメソッドの呼出し側に必要なすべてのアクセス権がなくても、これを呼び出すことで、そのメソッドでアクセス権のチェックを必要とする1つ以上のアクションを実行できるようにする場合です。たとえば、現在のメソッドが内部実装に固有の目的でファイルを開いたり、ネットワーク要求を実行したりする必要がある場合があります。
JDK 8より前のバージョンでは、doPrivileged
メソッドの呼出しには2つのパラメータしかありませんでした。これらは、呼出し側のメソッドに一時的な特権を付与し、アクセスをチェックするための通常の完全なスタックのトラバーサルを、必要なアクセス権がないクラスで定義されたメソッドに到達する可能性がある呼出しスタックまで継続せずに、そのクラスに到達したときに停止することで機能しました。通常、doPrivileged
を呼び出しているクラスは、そのコード・パスでは不要な追加のアクセス権を持っている可能性があり、それらは一部の呼出し側クラスにもない可能性があります。
通常、これらの追加権限は実行時に使用されません。doPrivileged
を使ってこれらを昇格させないことで、意図しないアクションを実行する可能性がある不正なコードの利用を防ぐことができます。これは特に、PrivilegedAction
が通常より複雑な場合や、時間の経過とともに独立して発展する可能性があるクラスやパッケージの境界の外側にあるコードを呼び出す場合に当てはまります。
3つのパラメータを持つdoPrivileged
のバリアントは、必要とされる予定がないアクセス権を不必要に昇格しないため、一般により安全に使用できます。ただし、実行時の効率が低いため、単純なコード・パスやパフォーマンス重視のコード・パスでは使用されない場合があります。
現在のメソッドをコーディングするときに、アクションを実行するために呼出し側のメソッドのアクセス権を一時的に拡張する必要がある場合があります。たとえば、フレームワークのI/O APIに、特定のデータ形式のファイルを開くための汎用メソッドが含まれる場合があります。このAPIは、通常のファイル・パス・パラメータを取り、それを使って基盤となるFileInputStream
を開くときに、呼出し側のコードのアクセス権を使用します。ただし、これによってすべての呼出し側が標準のデモ用サンプルを含む特別なディレクトリ内のデータ・ファイルを開くこともできるようになる可能性があります。
このAPIの呼出し側には、読取りアクセス用のFilePermission
を直接付与できます。ただし、呼出し側のコードのセキュリティ・ポリシーの更新が不便または不可能になる可能性があります。たとえば、呼出し側のコードがサンドボックス内で実行されるアプレットである可能性があります。
これを実装する1つの方法は、コード内で受け取ったパスをチェックし、それが特別なディレクトリ内のファイルを参照しているかどうかを判定することです。参照している場合は、doPrivileged
を呼び出してすべてのアクセス権を有効にしてから、PrivilegedAction
の内部でファイルを開きます。ファイルが特別なディレクトリに含まれていない場合は、コード内でdoPrivileged
を使用せずにファイルを開きます。
この手法では、実装は要求されたファイル・パスを慎重に扱い、それが特別な共有ディレクトリを参照しているかどうかを判定する必要があります。doPrivileged
を呼び出す前にファイル・パスを正規化して、パスが特別なディレクトリ内のファイルを参照しているかどうかを判定する前に、すべての相対パスが処理されるように(およびuser.dir
システム・プロパティを読み取るためのアクセス権がチェックされるように)する必要があります。また、特別なディレクトリの外側に抜ける意図を持つ悪質な"../"パス要素を防止する必要もあります。
より簡単かつ適切な実装では、3つ目のパラメータを持つdoPrivileged
のバリアントを使用します。特別なディレクトリへの読取りアクセスを含むFilePermission
を3つ目のパラメータとして渡します。その後、PrivilegedAction
の内部でファイルの操作を行います。この実装はより簡単で、セキュリティ上の欠陥を含む可能性がかなり低くなります。
このAPIとリフレクションの相互作用は、注意の必要な微妙な点です。doPrivileged
メソッドは、java.lang.reflect.Method.invoke
メソッドを使ってリフレクションとして呼び出すことができます。この場合、特権モードで与えられる特権は、Method.invoke
の特権ではなく、それによって呼び出されるリフレクションでないコードの特権です。このようにしないと、システム特権が誤って(または悪意を持って)ユーザー・コードに与えられてしまう危険があります。既存のAPIでリフレクションを使う場合にも、同様の要件が適用されます。