JavaTM安全体系结构(JDK1.2)
4. 访问控制机制和算法
4.1 java.security.ProtectionDomain
这个类代表了Java应用环境中的一个保护单元,它典型地与 "主体(principal)" 的概念发生关联,这里的主体是在计算机系统中被授予许可的一个实体。
从概念上讲,一个域是一个类的集合,这些类的实例被授予了相同的许可集。目前,一个域单独地由一个代码来源(CodeSource)来鉴别,它封装了在该域中运行的代码的两个特性:代码基址(java.net.URL)和公共密钥证书集(属于java.security.cert.Certificate类型的),公共密钥对应于在该域中为所有代码签字的私有密钥。因而,由相同的密钥签字和来自相同URL的类被放在同一个域中。
一个域还包含在该域中授予代码的许可,它是由现行安全策略所决定的。
具有相同许可、但来自不同代码源的类属于不同的域。
一个类属于一个且仅属于一个ProtectionDomain。
注意:目前在JDK1.2中,保护域是作为一个类装载的结果"按要求"创建的。java.lang.Class中的getProtectionDomain方法可被用来查询与给定类相关联的保护域。 但你必须具有适当的许可(RuntimePermission "getProtectionDomain"),才能成功地调用该方法。
今天,作为JDK组成部分而提交的所有代码被认为是系统代码,它们运行于单独的系统域中。每个Applet和应用程序运行在它的适当的域中,这是由它的代码源所决定的。
有可能保证:在任意非系统域中的对象不能自动发现另一个非系统域中的对象。这种分割可通过仔细的类解析和装载而获得。例如,对不同的域使用不同的类装载器。然而,SecureClassLoader(或它的子类)可以(任其选择)从不同的域装载类,这样就允许这些类在相同名空间内共存(由类装载器分割)。
4.2 java.security.AccessController
使用AccessController类有三个目的,每个目的在下面都将被详述:
根据现行安全策略,决定是否允许对关键系统资源的访问。
将某些代码标识为 "特权" 代码,从而影响随后的访问决定。
获得当前调用上下文关系的"快照",这样可以根据不同的上下文关系来作出访问控制决定。
控制对系统资源访问的任何代码如果希望使用特别的安全模型或存取由AccessController方法调用的存取控制算法,则它应该调用AccessController方法。另一方面,如果应用程序希望将安全模型延迟到SecurityManager在运行时被安装,则它应该调用在SecurityManager中的相应的方法。
例如,典型的调用访问控制的方法一直为如下代码(取自JDK的早期版本):
ClassLoader loader = this.getClass().getClassLoader();
if (loader != null) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead("path/file");
}
}
在新的体系结构下,无论是否有与一个调用类相关联的类装载器,都应该典型地调用检查。这可能是简单的,例如:
FilePermission perm = new FilePermission("path/file", "read");
AccessController.checkPermission(perm);
类AccessController的方法checkPermission检查当前执行程序的上下文,并就是否允许所请求的访问,作出正确决策。如果请求被允许,这个检查将安静地返回;否则,将扔出一个AccessControllerException(java.lang.SecurityException 的一个子类)的异常。
注意:有一些(原有的)典型事例。例如,在某些浏览器中,是否安装SecurityManager意味着这样或那样的安全状态,它可能会导致采取不同的动作。出于向后兼容方面的考虑,可以调用SecurityManager中的checkPermission方法:
SecurityManager security = System.getSecurityManager();
if (security != null) {
FilePermission perm = new FilePermission("path/file", "read");
security.checkPermission(perm);
}
目前,我们不改变SecurityManager在这方面的使用,但鼓励应用程序开发员在内置访问控制算法合适时,在编程中使用新版JDK技术。
SecurityManager的方法checkPermission缺省行为实际是调用AccessController的方法 checkPermission。一个不同的SecurityManager实现可能实现它自己在安全管理上的做法,有可能包括:在决定一个访问是否被允许时是否增加进一步的限制。
4. 2.1检查许可的算法
假设访问控制检查发生在一个具有多重调用者链的计算线程内(将它作为一个交叉保护域界限的多重方法调用来考虑)。如下图所示:
当AccessController的checkPermission方法被一个最近的调用者所调用时(例如:在File类中的一个方法),决定是否允许一个访问请求的基本算法如下:
如果在调用链中的任何调用者不具备所请求的许可,则异常AccessControlException被扔出。这里有一个例外:一个调用者(它的域被授予了上述许可)被标识为 "特权" (见下一部分),并且被调用者所调用的所有各方都具有(直接或间接地)上述许可。
明显地,有两种实现策略:
在一个 "eager evaluation" 实现中,无论一个线程在何时进入一个新的保护域,或从它退出,有效许可集都被动态更新。
上述策略的优点是:简化并加快了一个许可是否被允许的检查程序;而它的缺点是:因为对许可的检查比交叉域的调用的发生频率要小,所以大量许可的更新都似乎是无用的工作。
在一个"lazy evaluation" 实现中,无论许可检查在何时被请求,线程状态(如当前状态所反映的,包括当前线程的调用栈或它的等价物)都被检查,并且做出否定或准予特定访问的决策。
这种方法的一个潜在的缺点是,在许可检查时性能有所下降,尽管这种下降在 "eager evaluation" 做法中也可能发生(虽然发生在早些时候并且是在每个交叉域的调用中扩散)。到目前为止,我们的实现已经产生了可以接受的性能,所以,我们感觉 "lazy evaluation"是一种最经济实惠的做法。
从而,目前检查许可的算法是作为 "lazy evaluation"来实现的。 假设当前线程以调用者1,调用者2,直至调用者m的顺序遍历m个调用者,而调用者m调用了checkPermission方法。checkPermission用来确定访问是否被准予的基本算法如下:
i = m;
while (i > 0) {
if (caller i's domain does not have the permission)
throw AccessControlException
else if (caller i is marked as privileged)
return;
i = i - 1;
};
4.2.2特权处理
一个新的在AccessController类中的静态方法,允许在一个类实例中的代码通知这个AccessController:它的代码主体是享受"privileged(特权的)",它单独负责对它的可得的资源的访问请求,而不管这个请求是由什么代码所引发的。
这就是说,一个调用者在调用doPrivileged方法时,可被标识为 "特权"。在做访问控制决策时,如果checkPermission方法遇到一个通过doPrivileged调用而被表示为 "特权"的调用者,并且没有上下文自变量(见随后的有关上下文自变量的章节),checkPermission方法则将终止检查。如果那个调用者的域具有特定的许可,则不做进一步检查,checkPermission安静地返回,表示那个访问请求是被允许的;如果那个域没有特定的许可,则象通常一样,一个异常被扔出。
"特权"特性的正常使用如下所示:
如果你不需要从"特权"块内返回一个值,按下列代码去做:
somemethod() {
...normal code here...
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// privileged code goes here, for example:
System.loadLibrary("awt");
return null; // nothing to return
}
});
...normal code here...
}
PrivilegedAction是一个界面,它带有一个被称为run的方法,这个方法返回一个Object。上述例子显示了一个用来实现那个界面的匿名内类的创建,并提供了一个run方法的具体实现。当做一个doPrivileged调用时,一个PrivilegedAction实现的实例被传递给它。doPrivileged方法在使特权生效后,从PrivilegedAction实现中调用run方法,并返回run方法的返回值以作为doPrivileged的返回值,这一点在本例中被忽略。
(有关内类的进一步信息,请见Inner Classes Specification,它位于
http://java.sun.com/products/jdk/1.1/docs/guide/innerclasses/spec/innerclass
或More Features of the Java Language的某些章节,包括:
http://java.sun.com/docs/books/tutorial/java/more/nested.html 和
http://java.sun.com/docs/books/tutorial/java/more/innerclasses.html)
如果你需要返回一个值,你可按如下方法去做:
somemethod() {
...normal code here...
String user = (String) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return System.getProperty("user.name");
}
}
);
...normal code here...
}
如果用你的run方法执行的动作可能扔出一个"检查"的异常(包括在一个方法的throws子句列表中),则你需要使用PrivilegedExceptionAction界面,而不是使用PrivilegedAction界面:
somemethod() throws FileNotFoundException {
...normal code here...
try {
FileInputStream fis = (FileInputStream)
AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws FileNotFoundException {
return new FileInputStream("someFile");
}
}
);
} catch (PrivilegedActionException e) {
// e.getException() should be an instance of
// FileNotFoundException,
// as only "checked" exceptions will be "wrapped" in a
// PrivilegedActionException.
throw (FileNotFoundException) e.getException();
}
...normal code here...
}
有关被授予特权的一些重要事项:首先,这个概念仅存在于一个单独线程内。一旦特权代码完成了任务,特权将被保证清除或作废。
第二,在这个例子中,在run方法中的代码体被授予了特权。然而,如果它调用无特权的不可信代码,则那个代码将不会获得任何特权;只有在特权代码具有许可并且在直到checkPermission调用的调用链中的所有随后的调用者也具有许可时, 一个许可才能被准予。
有关使代码获得特权的详细信息, 请参见:
http://java.sun.com/products/jdk/1.2/docs/guide/security/doprivileged.html
4.3 访问控制上下文的继承
当一个线程创建一个新的线程时, 一个新的栈被创建。如果在这个新线程被创建时, 当前安全上下文未被保留, 则当AccessController.checkPermission在新线程内被调用时, 安全决策只能根据新线程的上下文来做出, 而不考虑父辈线程。
这个清除栈的问题, 从本质上来讲,并不是栈自身的问题, 但它使得安全代码的编写, 特别是系统代码的编写容易出现错误。例如, 一个非专业开发人员极有可能设想: 一个子线程(例如, 一个未引入不可信代码的子线程)将从父辈线程(例如, 一个引入不可信代码的父辈线程) 继承相同的安全上下文。这会在从新线程内部访问控制资源时(然后将资源传递给不太可信的代码), 引起不可预料的安全性空穴(如果父辈上下文在事实上未被存储)。
这样, 当一个新线程被创建时, 我们实际要保证(通过线程创建和其它代码)它能在子线程被创建时, 自动继承父辈线程的安全上下文。用这样的方法, 在子线程中的随后的checkPermission调用将考虑到所继承的父辈上下文。
换言之, 逻辑线程上下文被扩展, 从而既包括父辈上下文(以一个AccessControlContext的形式, 下面还将讨论), 也包括当前上下文; 检查许可的算法被扩展为如下形式(回想一下, 一直到调用checkPermission, 一共有m个调用者。有关AccessControlContext checkPermission方法的信息, 请见下一节):
i = m;
while (i > 0) {
if (caller i's domain does not have the permission)
throw AccessControlException
else if (caller i is marked as privileged)
return;
i = i - 1;
};
// Next, check the context inherited when
// the thread was created. Whenever a new thread is created, the
// AccessControlContext at that time is
// stored and associated with the new thread, as the "inherited"
// context.
inheritedContext.checkPermission(permission);
注意: 这个继承是可以递推的。例如, 一个孙辈既继承它的父辈, 也继承它的祖父辈。
还要注意的是, 继承的上下文快照是在新的子辈被创建时完成的, 而不是在子辈在第一次运行时完成的。继承特性不能在公共API中改变。
4.4 java.security.AccessControlContext
回想一下, AccessController的checkPermission方法执行安全检查是在当前执行线程的上下文中进行的(包括继承的上下文)。当这样的安全检查只能在不同的上下文中进行时, 将会出现困难。这就是, 有时候应该在给定的上下文中进行的安全检查, 实际上需要在不同的上下文中进行。例如, 当一个线程将一个事件登记在另一个线程中时, 服务于请求事件的第二个线程将没有真正的上下文来完成访问控制(如果该服务请求对控制资源的访问)。
为说明这个问题, 我们提供AccessController的getContext方法和AccessControlContext类。getContext方法为当前调用上下文照一个 "快照", 并将它放在一个该方法将返回的AccessControlContext对象中。举例如下:
AccessControlContext acc = AccessController.getContext ();
这个上下文捕捉有关的信息, 这样, 一个访问控制决策就可在不同的上下文中通过与这个上下文信息进行对比检查而做出。例如, 一个线程可将一个请求事件登记在第二个线程中, 同时提供这种上下文信息。AccessControlContext本身具有一个checkPermission方法, 它可根据它所封装的上下文(而不是当前执行线程的上下文)做出访问决策。这样, 如果需要的话, 第二个线程可通过调用如下代码而执行一个适当的安全性检查:
acc.checkPermission (permission);
上述方法调用与在第一个线程的上下文中执行相同的安全检查是等价的, 尽管它是在第二个线程中完成的。
一个或多个许可必须对照访问控制上下文进行检查的情况, 也时有发生, 但是哪个许可被优先检查则不清楚。在这种情况下, 你可以使用doPrivileged方法:
somemethod() {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// Code goes here. Any permission checks from
// this point forward require both the current
// context and the snapshot's context to have
// the desired permission.
}
}, acc);
...normal code here...
现在, 可以给出由AccessController的checkPermission方法所采用的全部算法。假设当前线程以调用者1, 调用者2, 直至调用者m的顺序遍历m个调用者,而调用者m调用了checkPermission方法。checkPermission用来确定访问是否被准予的算法如下:
i = m;
while (i > 0) {
if (caller i's domain does not have the permission)
throw AccessControlException
else if (caller i is marked as privileged) {
if (a context was specified in the call to doPrivileged)
context.checkPermission(permission);
return;
}
i = i - 1;
};
// Next, check the context inherited when
// the thread was created. Whenever a new thread is created, the
// AccessControlContext at that time is
// stored and associated with the new thread, as the "inherited"
// context.
inheritedContext.checkPermission(permission);
..........|Next|..........
欢迎与我们联系:webmaster@prc.sun.com
版权所有 1997-1998 Sun(中国)公司,北京南礼士路66号建威大厦16层
All rights reserved.Legal Terms