S-JIS[2007-06-21/2009-04-15] 変更履歴

java.security

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に載っている。


セキュリティーチェックの仕組み

これらのセキュリティーチェックは、各ライブラリーの中でSecurityManagercheckXXXメソッドを呼ぶことで行っている。
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()は、以下のようにして使用する。

sample1/src/SecuritySetProperty.java:
sample1/classes/SecuritySetProperty.class)

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);			//プロパティーをセットする権限が必要
	}
}

sample2/src/Main.java:
sample2/classes/Main.class)

/** 呼び出すクラス */
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"));		
	}
}

権限の設定例(sample.policy):

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;
	}

Java目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま