前面我们说沙箱就相当于存取控制器,java类载入器对于存取控制器来说是重要的依赖,代码基的权限集合是由类装载器创建的
话题:类加载在安全操作上的用处:
----能为每个类建立保护域。如在http://www.hkc.edu.cn/java/class/test.jar URL 上载入类字节码,http://www.hkc.edu.cn/java/class/test.jar 和jar的签名机构对应的证书(证书可以来自其他的证书库)将会构造代码基,同时类加载器还会为这个代码基建立权限集集合(Permissions类型对象)
----能结合虚拟机定义名称空间
----可以让类加载器调用安全管理器,从而限制只能访问适当的类
一,名称空间和类加载器
//略
二,类载入体系结构
//略
三,实现类加载器
1,类装载器的主要方法
public Class loadClass(String name):唯一的公共方法,
原则:先访问父类是否加载了类,这对于安全是至关重要的,
例如:如果没有这个原则,我们就能编写一个String类 替换掉lang包的String,这是JAVA语言安全不允许的
protected Class findClass(String name)
创建对象大部分工作都由此方法完成
---选择合适的机制完成类载入:ftp ,http,file等方式
---负责创建与类相关的保护域
---调用difinaClass()创建对象
protected defindClass(String name,byte[] b,int off,int len)
处理一组字节码,设置类权限(根据findClass得到的保护域)等
protected PermissionCollection getPermissions(CodeSource cs)
注意:Permissions也是PermissionCollection的子类
默认调用Policy类的getPermissions()方法
对开发人员,我们可以覆盖这个方法,从而放弃默认的Policy类和java默认的配置策略,另外我们还能编写自己的Policy类达到同样的目的,后者很复杂,如何编写自己的Policy类详见 六,存取控制器一章
2,使用URL类载入器
以下例子为自定义的类加载器
import java.net.*;
import java.security.*;
public class CustomURLClassLoader extends URLClassLoader {
static URL urls[];
static {
try {
urls = new URL[2];
urls[0] = new URL("http://piccolo.East/~sdo/");
urls[1] = new URL("file:/home/classes/LocalClasses.jar");
} catch (Exception e) {
throw new RuntimeException("Can't create URLs " + e);
}
};
public CustomURLClassLoader() {
super(urls);
}
public final synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First check if we have permission to access the package.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(name.substring(0, i));
}
}
return super.loadClass(name, resolve);
}
protected Class findClass(final String name)
throws ClassNotFoundException {
// First check if we have permission to access the package.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageDefinition(name.substring(0, i));
}
}
return super.findClass(name);
}
protected PermissionCollection getPermissions(CodeSource codesource) {
// Use all the standard permissions, plus allow the code to
// exit the VM.
PermissionCollection pc = super.getPermissions(codesource);
pc.add(new RuntimePermission("exitVM"));
return pc;
}
}
3,使用SecureClassLoader类
如果我们不使用http 文件方式,而是从数据库加载字节码,我们需要继承此类来自定义加载器
例子:
import java.net.*;
import java.security.*;
import java.io.*;
public class CustomSecureClassLoader extends SecureClassLoader {
public final synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First check if we have permission to access the package.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(name.substring(0, i));
}
}
System.out.println("loading class " + name);
return super.loadClass(name, resolve);
}
protected Class findClass(final String name)
throws ClassNotFoundException {
// First check if we have permission to access the package.
// You could remove these 7 lines to skip the optional step 4.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageDefinition(name.substring(0, i));
}
}
// Now read in the bytes and define the class
try {
return (Class)
AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws ClassNotFoundException {
byte[] buf = null;
try {
buf = readClassBytes(name);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
CodeSource cs = getCodeSource(name);
Class c = defineClass(name, buf,
0, buf.length, cs);
System.out.println("defining class " + c);
return c;
}
}
);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
protected byte[] readClassBytes(String name) {
try {
// This is the standard technique to read a class
// from the file system; you must convert the . in
// the package name to the directory structure from
// which you're reading.
name = name.replace('.', File.separatorChar);
// We read classes from the directoy foo (plus the
// additional directories required by the package name).
DataInputStream dis = new DataInputStream(
new FileInputStream("foo" +
File.separatorChar + name + ".class"));
byte[] buf = new byte[dis.available()];
dis.readFully(buf);
return buf;
} catch (IOException ioe) {
ioe.printStackTrace();
return null;
}
}
protected CodeSource getCodeSource(String name) {
try {
return new CodeSource(new URL("file", "localhost", name),new java.security.cert.Certificate[0]);
} catch (MalformedURLException mue) {
mue.printStackTrace();
}
return null;
}
protected PermissionCollection getPermissions(CodeSource codesource) {
PermissionCollection pc = new Permissions();
pc.add(new RuntimePermission("exitVM"));
return pc;
}
}
仔细看上面的例子的findClass方法, 我们可以发现类加载器加载字节码以及定义类的流程都在特权域里面,其实URLClassLoader类也是一样用的特权,关于特权接口前面详细介绍过,请参考六,存取控制器章节
为什么要用特权代码,理解这一点很重要,如果不用特权,类加载器能否加载类将取决于用户的代码,这就要求管理员或者开发人员为每一个java代码都配置策略,只有这样,类加载才能得到访问网路或者文件等等操作,这是一件很庞大的工程,可以说是不可能完成的,想一下你在java.policy中对所有的代码都配置具体的权限,可能吗?
所以,类加载一般都是要在特权区执行,
其实,如果加载进来的代码我们认为不可信任,我们只要在策略文件中为这些代码基(或签名)做权限限定就行了。就算他被加载进来了,也会在受限的环境中运行。当然 ,类的权限可以不来自策略,你可以在类加载的时候通过getPermissions方法进行设定,但是此方法实在findclass()中完成的,他并不是特权代码,所以类的权限还是受到你的调用代码权限的制约。