S-JIS[2007-06-21/2009-04-15] 変更履歴
Javaのライブラリーは、セキュリティーチェックを行うように作られている。
実行時にセキュリティーポリシーを設定しておけば、許可した動作しか出来なくなる。
(許可されない動作を行おうとすると、例外が発生する)
ローカルで実行されるJavaアプリケーションは、デフォルトではセキュリティーチェックを行わない。
もともとは(?)、ネット上で動くアプリ(Javaアプレット)の動作制限を行う為のものらしい。
この制限を行ってJavaVMの実行を限定した範囲のことをサンドボックス(砂場)と呼ぶ。
|
|
|
実行時にSystemにセキュリティーマネージャーを登録すると、セキュリティーチェックが行われるようになる。
(何が実行できるかについては、ポリシーファイルで指定する)
セキュリティーマネージャーを登録するには、プログラム内でセキュリティーマネージャーを登録する方法と、javaコマンドのオプションでセキュリティーマネージャーを有効にする方法がある。
プログラム内で登録する例:
public static void main(String[] args) { System.setSecurityManager(new SecurityManager()); 〜 }
javaコマンドで有効にする例:
> java -Djava.security.manager 実行対象クラス
セキュリティー機能を有効にすると、セキュリティーポリシーファイルの内容にしたがってセキュリティー制限が設定される。
JavaVMで動く各ライブラリーでは、ポリシーファイル内で許可されたことしか出来なくなる。
デフォルトで読み込まれるセキュリティーポリシーファイルは『${java.home}/lib/security/java.policy
』。
(${java.home}
は、Javaがインストールされたディレクトリ)
独自に作ったポリシーファイルを適用するには、javaコマンドの実行オプションでポリシーファイルを指定する。
> java -Djava.security.manager -Djava.security.policy=ポリシーファイル 実行対象クラス
サンプルポリシーファイル.policy:
grant { permission java.util.PropertyPermission "test_prop", "write,read"; permission java.io.FilePermission "c:/temp/test.txt", "read,write"; //permission java.io.FilePermission "<<ALL FILES>>", "read,write"; };
J2EEサーバーでは独自のポリシーファイルを用意していたりする。
→Tomcat5のポリシーファイル
ポリシーファイルの中には、「grant」で許可する範囲を示し、「permission」で許可する権限を書く。[2009-04-15]
書かれなかった権限は不許可扱いとなる。(不許可となるように記述することは出来ない)
grant { permission 〜許可する権限〜 ; };
grantの直後(「{」の前)には、許可する権限の範囲(対象となるクラス)を書く。
分かり易いところでは、“クラスをロードした場所(コードベース)”単位で権限を許可できる。
何も書かなければ全てのクラスに対しての権限となる。
1つのポリシーファイルの中に複数のgrantを書くことが出来る。
grant { 〜 }; ←全てに対しての権限許可 grant codeBase "URL" { 〜 }; ←特定のコードベースに対しての権限許可
コードベースとは、ロードされるクラスの元となった場所。jarファイルとか、classesディレクトリー等のこと。[2009-04-15]
grant codeBase "file:C:/workspace/sample/classes/" { 〜 }; grant codeBase "file:C:/oracle/ora92/jdbc/lib/ojdbc14.jar" { 〜 };
指定するのはあくまで「コードベース」、つまり各パッケージのルートディレクトリーに相当する部分のみ。
パッケージ毎・個別のクラス毎に指定することは出来ない。
URLとしての指定なので、Windowsであってもスラッシュ「/」で区切る。
また、「*」や「-」をワイルドカードとして使用できる。
codeBaseの指定例 | 該当するクラスの例 | 説明 |
---|---|---|
file:C:/workspace/- | C:\workspace\配下のクラス C:\workspace\sample1\classes配下のクラス C:\workspace\sample2\classes配下のクラス C:\workspace\hoge.jar内のクラス C:\workspace\sample1\bin\hoge.jar内のクラス |
末尾が「/-」の場合、 サブディレクトリーまで再帰的に含めた全ての classファイル及びjarファイル。 |
file:C:/workspace/* | C:\workspace\配下のクラス C:\workspace\hoge.jar内のクラス |
末尾が「/*」の場合、その場所の classファイル及びjarファイル。 |
file:C:/workspace/ | C:\workspace\配下のクラス | 末尾が「/」の場合、その場所の classファイル。 |
アクセス権は、grantの{ }ブロックの中に「permission」から始めて権限のクラス名(と引数)を記述する。[2009-04-15]
1つのgrantのブロックの中に複数のpermissionを書くことが出来る。
grant { permission java.security.AllPermission; ←全権限を許可 };
grant { permission java.util.PropertyPermission "*", "read"; permission java.io.FilePermission "C:/temp/test.txt", "read"; };
${ }で、システムプロパティーを使うことが出来る。[2009-04-15]
grant codeBase "file:${user.dir}/classes" { permission java.io.FilePermission "${java.io.tmpdir}${/}temp${/}test.txt", "read"; };
特別なプロパティーとして、「${/}」がある。これは、Windowsだと「\\」、UNIXだと「/」になってくれる。
(ただし、codeBaseで指定する区切りはURLのものなので、Windowsでも「/」を使う。すなわち「${/}」は使わない)
「//」で行コメント、「/* 〜 */」でブロックコメントが使える。[2009-04-15]
ただしセキュリティーポリシーファイル内はUTF-8でないといけないので、日本語でコメントを書く場合は注意。
以下の場所にパーミッションの一覧がある他、APIのJavadocの個々のクラス (java.security.Permissionを継承したクラス)の場所にも説明がある。[2009-04-15]
ポリシーファイルには、何も書かないと不許可になる。
とりあえず動作させたい場合は、java.security.AllPermissionを使うと全権限が許可される。[2009-04-15]
grant { permission java.security.AllPermission; };
java.util.PropertyPermissionを設定すると、指定されたシステムプロパティーの読み書きを制限できる。
プロパティー制限.policy:
grant { permission java.util.PropertyPermission "test_prop", "write,read"; ←readとwriteを許可 permission java.util.PropertyPermission "*", "read"; ←全てのプロパティーのreadを許可 };
プログラムの中で「System.getProperty("test_prop")
」を実行するには、"read"が許可されていなければならない。
「System.setProperty("test_prop", "値")
」を実行するには、"write"が許可されていなければならない。
変更権限が無いシステムプロパティーに対して値をセットしようとすると、例外が発生する。
System.setProperty("java.vm.version", "abc")を実行した例:
Exception in thread "main" java.security.AccessControlException: access denied (java.util.PropertyPermission java.vm.version write) at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323) at java.security.AccessController.checkPermission(AccessController.java:546) at java.lang.SecurityManager.checkPermission(SecurityManager.java:532) at java.lang.System.setProperty(System.java:727)
java.io.FilePermissionを設定すると、指定されたファイルへの読み書きを制限できる。
プロパティー制限.policy:
grant {
permission java.io.FilePermission "c:/temp/test.txt", "read,write"; ←readとwriteを許可
//permission java.io.FilePermission "<<ALL FILES>>", "read,write";
};
ファイルを1つ指定すると、そのファイルの権限を設定する。パスの区切りは(Windowsの場合)「\\」でも「/」でもよい。
ファイル名を「<<ALL FILES>>」とすると、全ファイルの権限を設定する。
特定のディレクトリの全ファイルの権限を設定するには、ファイル名部分を「*」にする。
ただしこの場合、パスの区切りはWindowsなら「\\」、UNIXなら「/」(つまり環境依存のパス区切り)でなければならない。
(こういう時の為に${/}があるんだろうけど。[2009-04-15])
(Fileクラスで「new
File("c:/temp/text.txt")
」のように使う場合は、Windowsでも「\」も「/」もOKなのに)
permission java.io.FilePermission "c:\\temp\\*", "write";
サブディレクトリを再帰的に指定するには、「*」の代わりに「-」を指定する。
このワイルドカードをファイル名の一部に使うことはできない。(「*.txt
」とか「test*
」とかは不可)
この辺りのルールは、FilePermissionのJavadocに載っている。
これらのセキュリティーチェックは、各ライブラリーの中でSecurityManagerのcheckXXXメソッドを呼ぶことで行っている。
checkXXXメソッドはセキュリティーをチェックし、制限された動作であればSecurityExceptionの例外を発生させる。
例えばファイルを削除するにはFile#delete()を使うが、その中は以下の様にコーディングされている。
File.java:
public boolean delete() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkDelete(path); } return fs.delete(this); }
また、システムプロパティーの設定は以下の様にコーディングされている。
System.java:
public static String setProperty(String key, String value) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new PropertyPermission(key, SecurityConstants.PROPERTY_WRITE_ACTION)); } return (String) props.setProperty(key, value); }
見て分かる通り、各メソッドの中でSystem#getSecurityManager()を呼び出し、その戻り値(セキュリティーマネージャー)がnullのときはセキュリティーチェックを行わない。
だから、セキュリティーチェックを行うにはセキュリティーマネージャーを登録する必要があるというわけ。
したがって、自分で権限を確認するには、「sm = System.getSecurityManager()
」がnull以外であれば、
sm.checkなんちゃら(); sm.checkPermission(パーミッション); AccessController.checkPermission(パーミッション);
のいずれかを実行すればよい。(権限が無ければSecurityExceptionの例外が発生する)
(AccessControllerを直接呼び出す場合はSystemにセキュリティーマネージャーが登録されているかどうかは無関係なので、権限エラーの発生有無と実際の実行可否は無関係)
実際のセキュリティーチェック方法としては、Systemクラスにあるメソッドは古い(互換性の為に残っているらしい)ので、AccessController#checkPermission()を使う方法が推奨されているようだ。[2009-04-15]
AccessControllerでは、doPrivileged()という、特権ブロックを作って実行するメソッドが提供されている。[2009-04-15]
セキュリティーを有効にすると、コードベース等の指定によっては異なる権限のライブラリーが混在することになる。
権限の低いクラスから権限の高いライブラリーのクラスを呼び出した場合、低い権限で実行されることになる。(安全の為、実行権限は、メソッド呼び出しにおける階層・履歴の中で最も低い権限に合わせられてしまう)
その結果、ライブラリー内で権限不足で実行できない(SecurityExceptionの例外が発生する)という事態が起こり得る。
こういった場合に、そのライブラリーのコードを、(全体を通した低い権限でなく)そのライブラリーに与えられた権限で実行できるようにするのが、特権ブロックの役割。
特権ブロックと言っても、プログラム内で好き勝手な権限に変更できるわけではなく、
セキュリティーポリシー策定者(そのマシンにおけるポリシーファイルを書く・置く人)が、そのライブラリーに対して権限を与えておく(ポリシーファイルにそういった許可権限を書いておく)必要がある。
特権の意味:
×呼び出し先で自分より高い権限で実行する
○呼び出された側で(呼び出し元とは無関係に)自分自身の権限で実行する
逆に言えば、ライブラリーを作る(提供する)側は、呼び出し元のクラスの権限と同じ権限で実行したい場合は 特にdoPrivileged()を使わずにコーディングすればいい。
特権ブロックで実行する為のdoPrivileged()は、以下のようにして使用する。
import java.security.AccessController; import java.security.PrivilegedAction;
/** 呼ばれるクラス(ライブラリー) */ public class SecuritySetProperty { public void setHighProperty(final String key, final String value) { //当クラスに与えられている権限で実行する(特権ブロックで実行する) AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { System.setProperty(key, value); //プロパティーをセットする権限が必要 return null; } }); } public void setNormalProperty(String key, String value) { //呼出元と同じ権限で実行する System.setProperty(key, value); //プロパティーをセットする権限が必要 } }
/** 呼び出すクラス */ public class Main { public static void main(String[] args) { // System.setProperty("abc", "foo"); //プロパティーをセットする権限が必要 SecuritySetProperty ssp = new SecuritySetProperty(); ssp.setHighProperty("abc", "zzz"); // ssp.setNormalProperty("abc", "goo"); System.out.println(System.getProperty("abc")); } }
grant codeBase "file:C:/workspace/sample1/classes/" { permission java.util.PropertyPermission "*", "write, read"; ←ライブラリーは書き込みも可 }; grant { permission java.util.PropertyPermission "*", "read"; ←(その他の)全クラスは読み込みのみ可 };
C:\workspace> java -cp sample1\classes;sample2\classes -Djava.security.manager -Djava.security.policy=sample.policy Main zzz
doPrivileged()は、呼び出す側が使うのではなく、呼び出される側が自分自身
に与えられた本来の権限で実行する為に使う。
上記の例では、SecuritySetPropertyクラス(ライブラリー)に対してsample.policyファイルでプロパティーの書込権限を与えているが、その他のクラスには書込権限は与えていない。
なので、Mainクラスから直接System.setProperty()を呼ぶと、セキュリティー例外が発生する。
権限を与えられているSecuritySetPropertyクラス側でも、doPrivileged()を使わない(setNormalProperty())なら、呼出元と同じ権限で実行されるので、やはりセキュリティー例外が発生する。
ちなみに、doPrivileged()はジェネリクスを使うように定義されているが、Sunのサンプルはジェネリクスを使っていない。
ジェネリクスを使うと以下のような感じになる。
public String getHighProperty(final String key) { String value = AccessController.doPrivileged( new PrivilegedAction<String>() { @Override public String run() { return System.getProperty(key); //プロパティーの読込権限が必要 } }); return value; }