Java安全架構瞭解與使用

一、Java安全架構

在這裏插入圖片描述
Java從誕生開始就是面向網絡應用,JVM需要加載網絡傳輸過來的.class文件,即會加載遠程類,因此遠程類中對系統資源的訪問必須做限制,也就是所謂“沙盒模型”,將遠程代碼可執行的權限限制在沙盒中。

二、jdk安全檢查接口

2.1 Permission

Permission類代表一個權限,其中最重要的幾個方法如下:

方法 描述
boolean implies(Permission permission) 當前權限是否包含傳入的權限
String getName() 權限作用的目標,如文件權限,這裏爲文件路徑
String getActions() 對目標可執行操作的集合,英文逗號分隔,文件權限如"read,write"

注意Permission代表可執行的權限集合,沒有代表拒絕的Permission。

Permission使用示例:

//檢查是否有讀 d:/test.txt 文件的權限
AccessController.checkPermission(new FilePermission("d:/test.txt", "read"));

事實上,jdk的FileInputStream就執行了安全檢查:

    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        //...

最終,會調用到AccessController.checkPermission方法。
不過,jdk默認是沒有安裝SecurityManager的,這一點後面會說明,因此平常你寫的java程序可以訪問系統上任何文件不受限制,除非操作系統對用戶權限做了限制。

2.2 CodeSource

CodeSource代表代碼來源:

方法 描述
CodeSource(URL url, java.security.cert.Certificate certs[]) 構造方法描述了兩個主要的屬性。URL代表代碼來源,可能來源於網絡,如”http://foo/foo.jar",也可能來着本地文件系統,如"file:/repository/foo.jar"。Certificate代表簽名代碼的證書,一份代碼可被多個私鑰進行簽名,此時通過公鑰(X509證書)可以驗證代碼是否可信。

2.3 AccessController

AccessController是執行權限檢查的接口,主要方法:

方法 描述
public static void checkPermission(Permission perm) 執行權限檢查
public static native T doPrivileged(PrivilegedAction action) 在特權模式中執行權限檢查

如前所述,傳入構造號的Permission對象,系統檢查是否擁有對應權限,否則將拋出SecurityException,這是個RuntimeException的子類:

AccessController.checkPermission(new FilePermission("d:/test.txt", "read"));

那麼,特權模式的意義是什麼呢?

理解這個問題,要結合前面CodeSource的概念。假設class A來自file:/a.jar,class B來自file:/b.jar,定義如下:

public class A {
	public void a() {
		AccessController.checkPermission(new FilePermission("d:/test.txt", "read"));
	}
}

public class B {
	public void b() {
		A a = new A();
		//將拋出SecurityException
		a.a();
	}
}

假設系統賦予了來自a.jar的class讀取 “d:/test.txt” 文件的權限,而來自b.jar的class則沒有權限,那麼當調用B.b(),checkPermission是否應該通過呢?

答案是:

這裏奉行的是 最小權限原則,否則來自遠程的不可信代碼,通過調用系統代碼,就可以"爲所欲爲"了。

問題是,如果A.a()確實有訪問"d:/test.txt"的需求,並且確定訪問該文件不會對系統安全造成影響,該怎麼辦呢?

答案是使用特權模式,在這種模式下,只會考慮直接調用者擁有的權限,不會考慮上層調用者是否擁有相應的權限。

因此可以改成這樣:

public class A {
	public void a() {
				//在特權模式中執行權限檢查,不考慮上層調用者
				AccessController.doPrivileged(new PrivilegedAction<Void>() {

			@Override
			public Void run() {
				//由於a.jar被賦予了讀取權限,此處不會拋出異常
				AccessController.checkPermission(new FilePermission("d:/test.txt", "read"));
				//執行讀文件操作...
				return null;
			}
		});
	}
}

public class B {
	public void b() {
		A a = new A();
		//可以正常調用,雖然b.jar沒有讀d:/test.txt文件的權限
		a.a();
	}
}

2.4 SecurityManager

SecurityManager其實是AccessController的門面類,當然也可以自己定製SecurityManager,替換系統默認實現,默認則是調用AccessController執行安全檢查。

系統默認不啓用SecurityManager,因此調用System.getSecurtityManager()將返回null。開啓安全管理器需要添加系統屬性:

-Djava.security.manager

基本調用方式:

 SecurityManager security = System.getSecurityManager();
 		//檢查系統是否安裝了SecurityManager
        if (security != null) {
            security.checkPermission(permission);
        }

2.5 Policy

講了半天安全檢查,那麼,系統是怎麼確定代碼權限的呢?答案是通過policy文件。
policy文件位置查找:

  1. jdk/jre/lib/security/java.security中,policy.url.n屬性
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy
  1. 通過系統屬性指定:
-Djava.security.policy=d:my.policy

policy文件格式,以下面將用到的my.policy文件爲例:

grant codeBase "file:///D:/spaces/ssp/securityTest/bin" {
  permission "java.lang.RuntimePermission" "accessDeclaredMembers";
  permission "java.lang.RuntimePermission" "createClassLoader";
  permission "java.lang.RuntimePermission" "closeClassLoader";
  permission java.io.FilePermission "d:/test.txt", "read";
  permission java.io.FilePermission "d:/testjar/test.jar", "read";
  permission otaku.security.MyPermission "hello", "action1,action2";
};

grant codeBase "file:///d:/testjar/test.jar" {
  permission java.io.FilePermission "d:/test.jar", "read";
};

基本格式:

grant codeBase <代碼位置> {
	permission <權限類全類名> <目標名> <允許的操作集合>;
	...
}

三、基本使用

3.1系統架構

系統架構
其中SecurityTest是主類,MyPermission是自定義的權限類,使用的policy文件爲d:/my.policy,內容如下:

grant codeBase "file:///D:/spaces/ssp/securityTest/bin" {
  permission "java.lang.RuntimePermission" "accessDeclaredMembers";
  permission "java.lang.RuntimePermission" "createClassLoader";
  permission "java.lang.RuntimePermission" "closeClassLoader";
  permission java.io.FilePermission "d:/test.txt", "read";
  permission java.io.FilePermission "d:/testjar/test.jar", "read";
  permission otaku.security.MyPermission "hello", "action1,action2";
};

grant codeBase "file:///d:/testjar/test.jar" {
  permission java.io.FilePermission "d:/test.jar", "read";
};

啓動參數:

-Djava.security.manager -Djava.security.policy=d:/my.policy

3.2代碼實現

3.2.1 MyPermission

自定義權限類,擴展權限系統。

package otaku.security;

import java.security.Permission;
import java.util.Arrays;
import java.util.List;

/**
 * 	自定義權限類,通過name和actions確定是否擁有相關權限
 * TODO 考慮序列化安全
 * @author ljf
 *
 */
public class MyPermission extends Permission {
	private static final long serialVersionUID = 1L;
	
	private String actionStr;
	private List<String> actions;

	public MyPermission(String name, String actions) {
		super(name);
		this.actionStr =actions;
		this.actions = Arrays.asList(actions.split(","));
	}

	@Override
	public boolean implies(Permission other) {
		if (!(other instanceof MyPermission)) {
			return false;
		}
		MyPermission otherMy = (MyPermission)other;
		return getName().equals(other.getName()) && actions.containsAll(otherMy.actions);
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof MyPermission)) {
			return false;
		}
		MyPermission otherMy = (MyPermission)other;
		if (!otherMy.getName().equals(getName())) {
			return false;
		}
		if (!(actions.size() == otherMy.actions.size() && actions.containsAll(otherMy.actions))) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		return actionStr.hashCode();
	}

	@Override
	public String getActions() {
		return actionStr;
	}

}

3.2.2 TestUtil

來自d:/testjar/test.jar的class,擁有讀d:/test.jar的權限。

package otaku.security;

import java.io.FilePermission;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

public class TestUtil {
	public static void read() throws PrivilegedActionException {
		//必須在特權模式下執行,因爲上層調用者沒有相關權限
		AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
			public Void run() throws Exception {
				AccessController.checkPermission(new FilePermission("d:/test.jar", "read"));
				return null;
			}
		});
	}
}

3.2.3 SecurityTest

程序主類:

package otaku.security;

import java.io.FilePermission;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;

public class SecurityTest {
	
	public static void main(String[] args) throws Exception {
		AccessController.checkPermission(new FilePermission("d:/test.txt", "read"));
		AccessController.checkPermission(new MyPermission("hello", "action1"));
		AccessController.checkPermission(new MyPermission("hello", "action1,action2"));
		
		try {
			//policy中沒賦予action3
			AccessController.checkPermission(new MyPermission("hello", "action1,action2, action3"));
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		
		//加載其他代碼源
		URLClassLoader classLoader = new URLClassLoader(new URL[] {
				new URL("file:///d:/testjar/test.jar")
		});
		
		Class<?> clazz = classLoader.loadClass("otaku.security.TestUtil");
		Method method = clazz.getDeclaredMethod("read");
		method.invoke(null);
		
		classLoader.close();
		
		System.out.println("over");
	}
	
}

執行結果:
在這裏插入圖片描述
權限系統運行正常,驗證完畢。

四、參考書目

<<Java2平臺安全技術>> by 宮力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章