JavaTM安全体系结构(JDK1.2)
3. 许可和安全策
3.1 许可类?
许可类表示了对系统资源的访问。java.security.Permission类是一个抽象的类,且在适当的时候可生成子类以表示特定的访问。
作为许可的一个例子,下列代码可被用来生成一个阅读在/tmp目录下名为“abc”的文件的许可?br>
perm = new java.io.FilePermission ("/tmp/abc", "read");
新许可类可通过继承Permission类或它的子类(如java.security.BasicPermission) 来生成的。已成为子类的许可(不是BasicPermission)通常都属于它们自己的包。因此,FilePermission可在java.io.package中找到。
一个重要的并需要被每个许可新类实现的抽象方法是implies方法。一般来说,“a implies b”意味着如果你被授予了许可"a",那么你也就自然地被授予了许可"b"。这在访问控制决策中是十分重要的。
与抽象类java.security.Permission一起的是被称为java.security.PermissionCollection的抽象类和叶子类java.security.Permissions。
java.security.PermissionCollection类表示了单一类别的(如文件许可)Permission对象的集合(例如,允许复制的一个集),以便于分组。在许可能够被以任何顺序添加到PermissionCollection对象的情况下(如为文件许可),当implies功能被调用时,PermissionCollection对象能够确保其后语义的正确性是至关重要的。
java.security.Permissions类表示了Permission对象的集合的集合,或换句话说,是异类许可的超级集合。
应用程序可添加系统支持的许可的新类型。添加此种特殊应用程序的许可的方法将在后面讨论。
现在,我们来说明所有内置许可的句法和语义。
3.1.1 java.security.Permission?
这个抽象类是所有许可的祖先,它为所有许可定义了所需的基本功能。
典型地,每个许可实例通过将一个或多个字符串参数传递给构造函数而被生成。在有两个参数的普通情况下,第一个参数通常是“目标名”(如作为许可目标的一个文件的名称),第二个参数是动作(如对一个文件的“阅读”动作)。一般的,一组动作可用逗号分隔的复合串来一起指定。
3.1.2 java.security.PermissionCollection
这个类掌握了许可的一个同类收集。换言之,类的每个实例仅掌握同类型的许可。
3.1.3 java.security.Permissions
设计这个类是为了掌握许可的异类收集。基本上,它是java.security.PermissionCollection对象的收集。
3.1.4 java.security.UnresolvedPermission
在正常情况下,一个安全策略的内部状态是由与每个代码源相关联的许可对象来表示的。然而,鉴于Java技术的动态性,当该策略被启动时,那些用来实现特定许可类的实际代码可能还没有在Java环境中被装载和定义。例如,一个基准许可类可能在JAR文件中,而该文件将稍后再装载。
UnresolvedPermission类被用来掌握这种“未解决的”的许可。类似的,java.security.UnresolvedPermissionCollection类储存UnresolvedPermission许可的收集。
在对一个以前未解决的类型(但它的类已经被装载)的许可的访问控制检查中,未解决的许可是“解决的”,并且作出了适当的访问控制决策。即:如果可能的话,基于UnresolvedPermission的信息,适当的类的新的对象被实例化,这个新的对象代替了UnresolvedPermission。
如果此时许可仍然是不可解决的,则该许可被认为是无效的,就象在一个安全策略中,它从未被授予过一样。
3.1.5 java.io.FilePermission
这个类的目标可用下列方法来说明,在这里,目录和文件名是不包括空格的字符串。
file
firectory (与directory/一样)
firectory/file
firectory/* (在目录directory下的所有文件)
? (在当前目录中的所有文件)
firectory/- (在目录directory 下的文件系统中的所有文件)
? (在当前目录下的文件系统中的所有文件)
《ALL FILES》" (在文件系统中的所有文件)
?
注意“”是一个用来表示系统中所有文件的特殊字符串。在Unix系统中,它包括了根目录下的所有文件;在MS-DOS系统中,它包括了在所有驱动器中的所有文件。
这个类的动作是:读、写、删除和执行(read, white, delete and execute)。以下是创建文件许可的有效代码样本:
port java.io.FilePermission;
FilePermission p = new FilePermission("myfile", "read,write");
FilePermission p = new FilePermission("/home/gong/", "read");
FilePermission p = new FilePermission("/tmp/mytmp", "read,delete");
FilePermission p = new FilePermission("/bin/*", "execute");
FilePermission p = new FilePermission("*", "read");
FilePermission p = new FilePermission("/-", "read,execute");
FilePermission p = new FilePermission("-", "read,execute");
FilePermission p = new FilePermission("《ALL FILES》", "read");
在这个类中的implies方法可正确地说明文件系统。例如,
FilePermission("/-","read,execute")隐含
FilePermission("/home/gong/public_html/index.html", "read");
FilePermission("/bin/*", "execute")隐含
FilePermission("bin/emacs19.13", "execute")。
注:这些字符串的大部分都是以依赖于平台的格式给出。例如,要表示对在Windows系统C驱动器中的 "temp" 目录下名为 "foo"的文件的阅读访问,你可以使用:
FilePermission p = new FilePermission ("c:\\temp\\foo", "read");
这里使用双反斜线来代表单反斜线是必要的。因为字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "\" 当作换行字符串来使用(例如, "\n"表示一个新的行),因此,我们需要用双反斜线来替代单反斜线的功能。在tokenizer对上述FilePermission目标字符串做过处理之后,再将双反斜线转换为单反斜线,最后结果是实际路径:
"c:\temp\foo"
在全局文件描述语言出现之前,字符串应该以依赖于平台的格式给出,这一点是必要的。还要注意的是,通配符(如 "*"和 "-"等)的使用影响了特殊文件名的使用。我们认为这是一个可以容忍的小限制。最后,要注意 "/-" 和 "《ALL FILES》" 在Unix系统中是同一个目标,它们都表示整个文件系统(如果有的话,它们也可表示多个文件系统);在其它操作系统中(如MS Windows,MacOS),这两个目标可能是不同的。
还要注意的一点是,象下列代码中仅指出目录和 "read" 动作的目标名,表示你只被允许列出那个目录中的文件名,而不能读它们。
FilePermission p = new FilePermission ("/home/gong", "read");
要允许对文件的阅读访问,你必须指出明确的文件名,或 "*",或 "- ",如下所示:?br>
FilePermission p = new FilePermission ("/home/gong/myfile", "read");
FilePermission p = new FilePermission ("/home/gong/*", "read");
FilePermission p = new FilePermission ("/home/gong/-", "read");
最后注意的是,代码总是自动具有对阅读位于与其相同的URL位置上的文件的许可,包括那个位置上的子目录;这不需要明确的许可。
3.1.6 java.net.SocketPermission
这个类表示通过sockets对一个网络的访问。这个类的目标可给为 "hostname:port_range",这里的hostname可用下列方式给出:
hostname (一个主机) IP address (一个主机) localhost (本地机) "" (相同于localhost") hostname.domain (在域domain中的一个主机) hostname.subdomain.domain *.domain (在域domain中的所有主机) *.subdomain.domain * (所有主机) ?/pre>?
也就是说,主机被表示为一个DNS名,或数字IP地址,或 "localhost"(为本地的机器),或 ""(它与指定的 "localhost" 等同)。
通配符 "*"可能会在DNS名称主机规范中使用一次,在这种情况下,它必须被放置在最左边,如: "*.sun.com"。 ?br>
port_range可按如下方式给出:
N (一个单一的端口)
N- (端口N及以上的所有端口)
-N (端口N及以上下的所有端口)
N1-N2 (N1和N2之间的所有端口, 包括N1和N2)
这里的N、N1和N2为非负整数,范围为0至65535(2^16-1)。 ?br>
在socket上的动作有accept(接受), connect(连接), listen(侦听)和resolve(分辨) (它们是基本的DNS查询)。请注意,动作 "resolve"是由 "accept"、 "connect"和 "listen"所暗指的,也就是说,那些可以侦听、接受来自主机的连接或启动一个指向另一主机的连接的动作的类,应该能够查询远程主机的名称。
以下是socket许可的一些实例:
import java.net.SocketPermission;
SocketPermission p = new SocketPermission ("java.sun.com", "accept");
p = new SocketPermission ( "204.160.241.99", "accept");
p = new SocketPermission ( "*.com", "connect");
p = new SocketPermission ( "*.sun.com:80", "accept");
p = new SocketPermission ( "*.sun.com:-1023", "accept");
p = new SocketPermission ( "*.sun.com:1024-", "connect");
p = new SocketPermission ( "java.sun.com:8000-9000", "connect,accept");
p = new SocketPermission ( "localhost:1024-", "accept,connect,listen");
?
请注意, SocketPermission ( "java.sun.com:80,8080", "accept") 和 SocketPermission ( "java.sun.com,javasun.sun.com", "accept")不是有效的socket许可。
另外,listen是仅用于本地主机端口的动作,而accept是不但可用于本地主机端口,也可应用于远程主机端口的动作。两个动作都是必需的。
3.1.7 java.security.BasicPermission?/b>
BasicPermission类继承了Permission类,它可被当作许可的基类,并按照相同的命名惯例称作BasicPermission。
一个BasicPermission的名称是特定许可的名称(例如,"exitVM", "setFctory", "queuePrintJob"等)。命名是按照所有等级命名惯例来进行。星号可能出现在名称的最后以表示一个通配符匹配,其前面可能有个 ".",也可能没有。例如: "java.*"或 "*"是有效的, "*java"或 "a*b"则是无效的。
不能使用动作串(从Permission继承)。因此,BasicPermission一般被用作“已命名的”许可的基类(这些许可包含名称但无动作列表;你可能有已命名的许可,也可能没有)。如果愿意,子类可能在BasicPermission的上层实现动作。
某些BasicPermission的子类有java.lang.RuntimePermission,
java.security.SecurityPermission,java.util.PropertyPermission, ?br>java.net.NetPermission ?/font>?
3.1.8 java.util.PropertyPermission
这个类的基本目标是设置在各种属性文件中的Java属性名称,例如 "java.home"和 "os.name" 属性。目标可被指定为 "*" (全部属性)、 "a.*"(其名称具有前缀a.的全部属性)以及 "a.b.*"等等。请注意,通配符只能出现一次,且必须位于最右端。
这个类是BasicPermission子类之一,它在BasicPermission上实现动作。该动作是读和写,其定义如下: "read" 许可允许在java.lang.System中的getProperty方法被调用以得到属性值, "white" 许可允许setProperty方法被调用以设置属性值。
3.1.9 java.lang.RuntimePermission
一个RuntimePermission的目标可用任何字符串来表示,且没有动作与该目标相关联。例如,RuntimePermission("exitVM")表示退出Java虚拟机的许可。
目标名为:
createClassLoader
getClassLoader
setContextClassLoader
setSecurityManager
createSecurityManager
exitVM
setFactory
setIO
modifyThread
stopThread
modifyThreadGroup
getProtectionDomain
readFileDescriptor
writeFileDescriptor
loadLibrary.{library name}
accessClassInPackage.{package name}
defineClassInPackage.{package name}
accessDeclaredMembers.{class name}
queuePrintJob
?
3.1.10 java.awt.AWTPermission
它与RuntimePermission相同,也是一个无动作的许可。其目标名为:
accessClipboard
accessEventQueue
listenToAllAWTEvents
showWindowWithoutWarningBanner
3.1.11 java.net.NetPermission
这个类包含下列目标且无动作:
requestPasswordAuthentication
setDefaultAuthenticator
specifyStreamHandler
3.1.12 java.lang.reflect.ReflectPermission
这是一个为进行反射操作的Permission类。一个ReflectPermission是一个命名的许可(与RuntimePermission类似)且无动作。目前定义的唯一名称是:
suppressAccessChecks?br>
它允许超越反射对象实施的标准Java编程语言的存取检查,即对其类中的公有的、缺省的、受保护的和私有成员的访问检查等。换句话说,当一个实例拥有该许可后,它可访问反射对象的公有的、缺省的、受保护的和私有成员。
3.1.13 java.io.SerializablePermission?br>
这个类包含下列目标并且无动作: ?br>
enableSubclassImplementation
enableSubstitution ?br>
3.1.14 java.security.SecurityPermission?br>
SecurityPermission控制对与安全相关的对象的访问,如对象Security, Policy, Provider, Signer和Identity等。这个类包含下列目标并且无动作:
getPolicy
setPolicy
getProperty.{key}
setProperty.{key}
insertProvider.{provider name}
removeProvider.{provider name}
setSystemScope
setIdentityPublicKey
setIdentityInfo
printIdentity
addIdentityCertificate
removeIdentityCertificate
clearProviderProperties.{provider name}
putProviderProperty.{provider name}
removeProviderProperty.{provider name}
getSignerPrivateKey
setSignerKeyPair
3.1.15 java.security.AllPermission
这个许可意味着全部许可。它是为简化系统管理员的工作而设计的,因为系统管理员可能需要执行多种要求全部(或大量)许可的任务。反复定义许可显然是烦琐的。请注意,AllPermission也意味着在未来定义的新的许可。 ?br>
当使用这个许可时,应慎重考虑。
3.1.16许可含义讨论
回想一下我们发现,许可经常被互相比较,而且,为了便于这种比较,我们要求每个许可类定义一个implies方法,它表示特定的许可类是如何与其它许可类发生关系的。例如,java.io.FilePermission("/temp/*", "read")隐含java.io.FilePermission("/temp/a.txt", "read"),但不隐含任何java.net.NetPermission。
另外一层含义可能对某些读者来说不能立即明了。假设一个Applet被授予了编写全部文件系统的许可,它假设允许该Applet来替换执行系统,包括JVM运行环境。这就有效地意味着Applet已经被授予了全部许可。 ?br>
另一个例子是,如果一个Applet被授予运行时创建类装载器的许可,它就被有效地授予了更多的许可。因为类装载器可执行敏感操作。 ?br>
其它一些“危险的”许可包括允许设置系统属性的许可、运行时定义包和装载本地代码库的许可(因为Java安全结构不能防止本地代码的恶意行为),当然,还有AllPermission。 ?br>
有关许可的更多信息,包括列举分配特殊许可的风险的表格和所有需要许可的JDK内置方法表格等,请查看: ?br>
?a href="http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html " target="_new">http://java.sun.com/products/jdk/1.2/docs
/guide/security/permissions.html
3.1.17如何创建新型许可
除了SUN Microsystems应该负责扩展内置于JDK中的许可外,无论是通过增加功能性,还是通过将附加目标关键词引入到一个类中(如java.lang.RuntimePermission),无人能够担当此任。这一点是必需的,它保持了JDK的连续性。
要创建一个新的许可,建议采用下面例子所示的步骤。假设ABC公司的一个应用程序开发员要创建一个“看电视”的用户化许可。
首先,创建一个新的类com.abc.Permission,它继承抽象类java.security.Permission(或它的一个子类);再创建另一个新的类com.abc.TVPermission,它继承com.abc.Permission。确认其它类中的implies方法被正确地实现(当然,com.abc.TVPermission可直接继承java.security.Permission;不需要中间的com.abc.Permission)。
public class com.abc.Permission extends java.security.Permission
public class com.abc.TVPermission extends com.abc.Permission
下图显示了子类之间的关系:
第二, 将这些新类加在应用系统包中。
每个想要允许这个新型许可的用户都要在策略文件中增加一个入口(策略文件句法的细节见以下章节)。授予来自http://java.sun.com/的代码许可,使其可以观看5频道电视的策略文件入口授权码为:
grant codeBase "http://java.sun.com/" {
permission com.abc.TVPermission "channel-5", "watch";
}
在应用程序的资源管理代码中,要查看一个许可是否应该被准予时,使用一个com.abc.TVPermission对象作为参数来调用AccessController的checkPermission方法。
com.abc.TVPermission tvperm = new
com.abc.TVPermission ("channel-5", "watch");
AccessController.checkPermission(tvperm);
请注意,当增加一个新的许可时,你应该创建一个新的(许可)类,而不在SecurityManager中增加新的方法(过去,为了检查访问的新类型,你必须在SecurityManager类中增加新的方法)。
如果更精细的TVPermission (例如, "channel-1:13"或"channel-*")被允许的话,则需实现一个TVPermissionCollection对象,这个对象知道如何处理这些伪名称。
为了执行内置访问控制算法,新的代码应该总是通过调用AccessController类的checkPermission方法来调用一个许可检查。没有必要检查是否具有ClassLoader或SecurityManager。另一方面,如果这个算法留给已安装的安全管理器类,则应该调用方法SecurityManager.checkPermission。
3.2 java.security.CodeSource
这个类继承了HTML内代码基址(codebase)的概念,目的是不仅封装代码位置(URL),而且封装包含公共密钥(这个公共密钥可用来校验来自那个位置的数字签字)的数字证书。请注意,这不是在HTML文件中的CodeBase标记的等价物。每个证书表示为java.security.cert.Certificate,每个URL表示为java.net.URL。
3.3 java.security.Policy
Java应用环境的系统安全策略说明了来自不同地方的代码的可用许可,它用Policy对象来表示。 在特别的情况下,用Policy子类来表示,该子类实现了Policy类中的抽象方法。
为了使一个Applet(或一个在SecurityManager下运行的应用程序)能被允许执行安全的动作,如阅读或编写文件,该Applet(或应用程序)必须被授予那个特定动作的许可。唯一例外是,代码总是自动具有阅读来自相同的出处的文件以及这个出处的子目录的文件的许可,而不需要明确的许可。
可能有多个Policy对象的实例,尽管在任何时候都仅有一个是“有效的”。通过调用getPolicy方法,可获得当前安装的Policy对象,并且,可通过调用setPolicy方法(由具有重设Policy的许可的代码来执行)来改变它。
当保护域启动它的许可集时,当前策略被一个ProtectionDomain所检查。保护域在一个CodeSource对象中通过,这封装了它的代码基址(URL)和证书属性。Policy对象评估全局策略并返回一个适当的Permission对象,它说明了来自指定代码源的代码的许可。
对Policy对象所采用的策略信息的资源定位等于Policy实现。策略的配置可被存储, 例如当作一个平面ASCII文件来存储,或当作一个Policy类的串行二进制文件来存储,或当作一个数据库来存储。有一个缺省Policy实现,它可获得来自静态策略配置文件的信息。
在制定安全策略时并没有涉及Policy对象。它只是策略配置在Java运行时的体现。
3.3.1策略文件格式
在缺省Policy实现中, 策略可在一个或多个策略配置文件中指定。配置文件指出, 对来自指定代码源的代码, 哪些许可是被允许的。
一个策略配置文件一般都包含一个入口列表。它可能包含一个密钥库(keystore)入口, 并包含零个或多个 "grant"入口。
密钥库是一个数据库, 它装有私有密钥和它们的相关数字证书, 如用来认证相应的公共密钥的X.509证书链。keytool实用程序用来创建并管理密钥库。在策略配置文件中指定的密钥库可被用来查询在文件准予入口中指定的签字者的公共密钥。 如果任何准予入口说明了签字者的别名, 则一个密钥库入口必须出现在一个策略配置文件中。
此时, 在策略文件中可能只有一个密钥库入口(第一个之后的被忽略), 并且, 它可出现在文件准予入口之外的任何地方。它具有如下句法:
keystore "some_keystore_url", "keystore_type";
这里, "some_keystore_url"指定keystore的URL位置, "keystore_type"指定密钥库类型。后者是可选项, 如果未指定, 则被假定为在安全属性文件中的 "keystore.type"属性所指定的类型。
URL与策略文件的位置相关。因此, 如果策略文件在安全属性文件中被指定为:
policy.url.l=http://foo.bar.com/blah/some.policy
并且该策略文件有一个入口:
keystore ".keystore";
则keystore将被从如下位置装载:
?http://foo.bar.com/blah/.keystore
URL也可以是绝对地址。
密钥库类型定义了密钥库信息的存储形式和数据格式, 并定义了算法以保护密钥库中的私有密钥以及密钥库本身的完整性。Sun Microsystems支持的缺省类型是被称为 "JKS"的专用密钥库类型。
在策略文件中的每个准予入口基本上是由一个CodeSource和它的许可所组成。实际上,CodeSource是由一个URL和一个证书集所组成,而一个策略文件入口包括一个URL和一个签名者列表。在查询了密钥库以确定指定签名者的证书之后,系统创建相应的CodeSource。
在策略文件中的每个准予入口采用以下格式。前面的 "grant"是一个保留字,它表示一个新的入口的开始,可选项出现在括号内。在每个入口内,第一个 "permission" 也是一个保留字,它标志着在该入口内的一个新的许可的开始。每个准予入口都将一个许可集授予一个指定代码源。
grant [SignedBy "signer_names"] [, CodeBase "URL"] {
permission permission_class_name [ "target_name" ]
[, "action"] [, SignedBy "signer_names"];
permission ...
};
在任何逗号之前或之后,允许空格。许可类的名称必须全部是合格的类名称,如java.io.FilePermission, 且不能简化(如简化为FilePermission)
动作字段是可选项,如果许可类不需要它,则可将其忽略。如果出现动作字段,则必须紧接在目标字段之后。
一个CodeBase URL值的确切含义取决于结尾处的字符。用 "/"
结尾的CodeBase可在指定目录中与所有类文件匹配(不包括JAR文件);用 "/*"
结尾的CodeBase可在指定目录中与所有文件匹配(包括类文件和JAR文件);用 "/-"
结尾的CodeBase可在指定目录中及那个目录下的子目录中与所有文件匹配(包括类文件和JAR文件)。
CodeBase字段(URL)是可选项,如果忽略,它表示“任意代码基址”。
第一个签字字段是通过单独机制映射的字符串别名,这个映射对应与签字者相关的公共密钥集(在密钥库中的证书内)的映射。这些密钥被用来校验某个已签字的类是否真的是由那些签字者所签的字。
包括多个签名的字段可以是用逗号分隔的字符串。例如, "Adam,Eve,Charles"表示这是一个由Adam和Eve和Charles签的字(也就是说,他们的关系是“和”,而不是“或”)。
这个字段是可选项,如果被忽略,则表示“任意签字者”;换句话说,“这个代码是否被签字都无所谓”。
第二个签字字段(在一个Permission入口内)代表了包括公共密钥的密钥库入口的别名,这个公共密钥对应于用来签写实现上述许可类的字节码的私有密钥。只有在该字节码的实现被校验为是上述别名的正确签字时,这个许可入口才是有效的(也就是说,访问控制许可是基于这个入口而发出的)。
CodeBase和SignedBy字段之间的顺序是无关紧要的。
以下是一个非正式的为Policy文件格式设计的BNF语法,这里的非大写项是终结符。
? ?
PolicyFile -> PolicyEntry | PolicyEntry; PolicyFile
PolicyEntry -> grant {PermissionEntry}; |
grant SignerEntry {PermissionEntry} |
grant CodebaseEntry {PermissionEntry} |
grant SignerEntry, CodebaseEntry {PermissionEntry} |
grant CodebaseEntry, SignerEntry {PermissionEntry} |
keystore "url"
SignerEntry -> signedby (a comma-separated list of strings)
CodebaseEntry -> codebase (a string representation of a URL)
PermissionEntry -> OnePermission | OnePermission PermissionEntry
OnePermission -> permission permission_class_name
[ "target_name" ] [, "action_list"]
[, SignerEntry];
现在我们给出一些实例。下面的策略将许可a.b.Foo授予由Roland签字的代码:
grant signedBy "Roland" {
permission a.b.Foo;
};
下面的例子将一个FilePermission授予所有代码(不管签字者和/或codeBase):
grant {
permission java.io.FilePermission ".tmp", "read";
};
下面的例子将两个许可授予由Li和Roland共同签字的代码:
grant signedBy "Roland,Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission java.util.PropertyPermission "user.*";
};
? 下面的例子将两个许可授予由Li签字的代码且来自http://java.sun.com:
grant codeBase "http://java.sun.com/*", signedBy "Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission java.io.SocketPermission "*", "connect";
};
? 下面的例子将两个许可授予由Li和Roland共同签字的代码,而且只有在由实现com.abc.TVPermission的字节码是由Li签字时才能成功:
grant signedBy "Roland,Li" {
permission java.io.FilePermission "/tmp/*", "read";
permission com.abc.TVPermission "channel-5", "watch",
signedBy "Li";
};
包括第二个签字者字段的原因是为了在一个许可类没有与Java运行安装在一起时防止欺骗。例如,com.abc.TVPermission的一个拷贝可作为一个远程JAR被下载,并且用户策略可能包括一个引用它的入口。因为该文档不是长命的,当com.abc.TVPermission类被第二次下载时(可能从不同的web地址),第二个拷贝是否可信,这一点是至关重要的,因为在用户策略中的许可入口的出现可能反映了用户对这个类字节码的第一个拷贝的信心和信任。
我们选择使用数字签字(而不是选择存储字节码的第一个拷贝的哈希值,然后在将第二个拷贝与之进行比较)以保证真实性的原因是,因为许可类的作者能合法地更改类文件以反映一个新的设计或实现。
请注意:文件路径字符串必须采用依赖于平台的格式;在一个全局文件描述语言出现之前,这是必要的。上面的例子已经显示了适用于Solaris系统的字符串。在Windows系统中,当你要用字符串直接指定一个文件路径时,你需要使用双反斜线来代替表示路径的单反斜线。举例如下:
grant signedBy "Roland" {
permission java.io.FilePermission "C:\\users\\Cathy\\*", "read";
};?/pre>
这是因为该字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "\"被用来当作转义字符串(例如: "\n"表示新的一行),这就要求用双反斜线来代替单反斜线。tokenizer处理上述FilePermission目标字符串时,将双反斜线转换为单反斜线,最终结果是实际路径:
"C:\users\Cathy\*"
? 3.3.2策略文件的属性扩展
在策略文件和安全属性文件中的属性扩展是可能的。
属性扩展与扩展在一个外壳上的变量相似。即:当如下字符串
"${some.property}"
出现在一个策略文件或一个安全属性文件中时,它将被扩展为指定系统的属性的值。例如:
permission java.io.Permission "${user.home}", "read";
将扩展 "${user,home}"以使用 "user.home"系统属性的值;如果那个属性的值是 "/home/cathy",则上述代码与下面的代码等价:
permission java.io.FilePermission "/home/cathy", "read";
为加速独立于平台的策略文件,你也可以使用特殊符号 “${/}",它是 "${file.separator}"的快捷方式。它允许在许可定义中使用。例如:
permission java.io.FilePermission "${user.home}${/}*", "read";
如果user.home为/home/cathy,并且你是在Solaris系统,上式可转换为:
permission java.io.FilePermission "/home/cathy/*", "read";
另一方面,如果user.home为C:\users\cathy,并且你是在Windows系统,则可转换为:
permission java.io.FilePermission "C:\users\cathy\*", "read";
另外,作为特殊情况,如果你在一个代码基址中扩展一个属性,如:
grant codeBase "file:/${java.home}/lib/ext/"
则任何file.separator字符将被自动转换为 /'s,因为代码基数是URLs,所以上述转换是我们所期望的。这样,在一个Windows系统上,甚至如果java,home被设置为C:\jdk1.2,上式也可转换为:
grant codeBase "file:/C:/jdk1.2/lib/ext/"
于是你不需要在代码基址字符串中使用${/}(你不应该使用)。
属性扩展可发生在策略文件中任何有双引号字符串的地方,这包括:signedby、代码基址、目标名和动作字段。
是否允许属性扩展,取决于安全属性文件中的 "policy.expandProperties"属性的值。如果这个值是true(缺省值),则允许扩展。
请注意:你不能使用嵌套的属性。例如:
"${user.${foo}}"
上式将不起作用,甚至 "foo"属性被设置为 "home",也不起作用。原因是属性语法分析程序不能识别嵌套属性。它只寻找第一个 "${",然后继续寻找,直到发现第一个 "}"为止,并且试图解释结果 "${user.$foo}"为一个属性。如果没有这样的属性,则失败。
另外还要注意,如果一个属性在一个准予入口、许可入口或密钥库入口不能被扩展,则那个入口将被忽略。例如:如果系统属性 "foo" 未被定义并且你具有:
grant codebase "$ {foo}" {
permission ?
permission ?
};
则所有在准予入口的许可将被忽略。如果你具有: ?
grant {
permission Foo "$ {foo}"
permission Bar;
};
则仅有"permission Foo ?入口被忽略。最后,如果你具有:
keystore "${foo }";
则密钥库入口被忽略。
最后注意一点,在Windows系统中,当你直接用字符串指定一个文件路径时,你需要使用双反斜线来代替在路径中的单反斜线:
"C:\\users\\cathy\\foo.bat"
这是因为该字符串是由一个tokenizer来处理的(java.io.StreamTokenizer),它允许 "\"被用来当作转义字符串(例如: "\n"表示新的一行),这就要求用双反斜线来代替单反斜线。tokenizer处理上述字符串时,将双反斜线转换为单反斜线,最终结果是:
"C:\users\Cathy\foo.bat"
在一个字符串中的属性扩展发生在tokenizer处理完该字符串之后。于是,如果你有字符串
"${user.home}\\foo.bat"
则tokenizer将首先处理该字符串,转换双反斜线为单反斜线,结果是:
"${user.home}\foo.bat"
然后 ${user.home}属性被扩展,最后结果是:
"C:\users\cathy\foo.bat"
假设user.home值为 "C:\users\cathy" (当然是独立于平台的),最好在一开始字符串就被指定,且不用任何显式斜线。如下所示:
"${user.home}${/}foo.bat"
3.3.3分配许可
当装载一个起源于特定CodeSource的新的类时,安全性机制查询策略对象以决定应准予什么样的许可。这是通过调用安装在VM上的Policy对象上的getPermission方法来完成的。
显然,一个特定的代码源可与多个入口所给定的代码源在该策略中相匹配。例如,因为允许使用通配符 "*"。
下列算法被用来在策略中定位适当的许可集:
1. 如果代码被签字的话,匹配公共密钥。
2.如果在策略中没有找到密钥,则忽略该密钥;如果每一个密钥都被忽略,则视该代码为未签字代码。
3. 如果密钥匹配或没有说明签字者{ 在策略中尝试为该密钥匹配所有的URLs }
4. 如果密钥或URL之一未得到匹配,使用内置的缺省的许可,该许可就是最初的沙箱许可。
一个策略入口CodeBase URL值的确切含义取决于结尾处的字符。用 "/"
结尾的CodeBase在指定目录中与所有类文件匹配(不包括JAR文件);用 "/*"
结尾的CodeBase在指定目录中与所有文件匹配(包括类文件和JAR文件);用 "/-"
结尾的CodeBase在指定目录中及那个目录下的子目录中与所有文件匹配(包括类文件和JAR文件)。
举例来说,在策略中给定了 "http://java.sun.com/- ",则在该web地址上的任意代码基址都将与策略入口匹配。匹配的代码基址包括
"http://java.sun.com/people/gong/appl.jar"。
如果多个入口被匹配,则所有在那些入口中所给定的许可将被准予。换言之,许可的分配是附加的。例如,如果用密钥A签字的代码获得了许可X,用密钥B签字的代码获得了许可Y,并且未指定特定的代码基基址,则由A和B共同签字的代码将获得许可X和Y。类似的,如果带有代码基址"http://java.sun.com/- "的代码获得了许可X, 带有代码基址 "http://java.sun.com/people/* "的代码获得了许可Y, 并且未指定特定签字者,则来自 "http://java.sun.com/people/applet.jar"的一个Applet将同时获得X和Y。
请注意,这里的URL匹配纯粹是句法上的。例如,一个策略可给定一个入口,这个入口指定一个URL "http://ftp.sun.com",只有在能够直接从ftp获得Java代码来执行时,这样的入口才是有用的。
为了为本地文件系统指定URLs,可使用文件URL。例如,为指定在Solaris系统中的/home/cathy/temp目录下的文件,你可以使用:
"file:/home/cathy/temp/*"
要指定在Windows系统中C驱动器上temp目录下的文件,你可以使用:
"file:/c:/temp/*"
注意:无论在什么平台上,代码基址URL总是使用斜线(不是反斜线)。
你也可以使用一个绝对路径名,如:
"/home/gong/bin/MyWonderfulJava"
3.3.4缺省系统和用户策略文件
在缺省Policy实现中,策略可在一个或多个策略配置文件中说明。配置文件指定什么样的许可能够为来自指定代码源的代码所使用。
一个策略文件可通过一个简单的文本编辑器来制作,也可以使用我们稍后将要讨论的图形Policy Tool实用程序来制作。
缺省地,我们具有一个单独的系统整体策略文件和一个单独的用户策略文件。
系统整体策略文件缺省地位于:
{java.home}/lib/security/java.policy (Solaris)?br>
{java.home}\lib\security\java.policy (Windows)
这里的java.home是一个系统属性,它指定JDK被安装的目录。
用户策略文件缺省地位于:
{user.home}/.java.policy (Solaris)
{user.home}\.java.policy (Windows)
这里的user.home是一个指定用户的起始目录的系统属性。
当Policy被初始化时,系统整体策略首先被装入,然后用户策略被添加上去。如果这两个策略都不出现,则一个内置策略被使用。这个内置策略与初始沙箱策略相同。
策略文件定位在安全属性文件中被指定,它位于:
{java.home}/lib/security/java.security (Solaris)
{java.home}\lib\security\java.security (Windows)
策略文件定位被指定为某个属性的值,它的名称为如下形式:
policy.url.n
这里的n是一个数。你可以用如下一行形式来指定每一个这样的属性值:
policy.url.n=URL
这里的URL是一个URL说明。例如,缺省系统和用户策略文件在安全属性文件中被定义为:
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${java.home}/.java.policy
实际上,你可以指定许多URLs,包括 "http://"形式的。所有指定的策略文件将被装载。你也可以注释或改变第二个URL,使缺省用户策略文件不能被阅读。
算法从policy.url.1开始,并保持递增直到它未发现一个URL为止。于是如果你具有policy.url.1和policy.url.3,则policy.url.3将总是读不到。
当调用一个应用程序的执行时,可能指定一个附加的或不同的策略文件。这可以通过 "-Djava.security.policy"命令行自变量来实现,它设置java.security.policy属性的值。例如,如果你使用:
java -Djava.security.manager -Djava.security.policy=pURL SomeApp
这里的pURL是一个URL,它指定一个策略文件位置,然后除了安全属性文件中指定的所有策略文件外,这个指定的策略文件也将被装载。( "-Djava.security.manager"自变量将保证缺省安全管理器被安装,并且应用程序服从于策略检查,就象 "Security Management for Applets and Applications"中所描述的那样。如果应用程序SomeApp安装一个安全管理器,则无必要)。
如果如下程序一样使用双等号,那么仅有指定的策略文件将被使用,其它将被忽略。
java -Djava.security.manager -Djava.security.policy = = pURL SomeApp
如果你要传递一个策略文件至appletviewer,请再次使用 "-Djava.security.policy"自变量,如下所示:
appletviewer -J-Djava.security.policy=pURL myApplet
请注意: 如果在安全属性文件中的 "policy.allowSystemProperty"属性被设置为假,"-Djava.security.policy"策略文件值将被忽略(对java 和 appletviewer都一样)。其缺省值为真。
3.3.5用户化策略评估
Policy类的当前设计不象以前那样复杂。我们已经充分地考虑了这个问题并谨慎地向前发展;部分原因是为了我们所设计的方法调用能够适合大多数的一般情况。同时,给出可选择的策略类以替代缺省策略类,只要前者是一个抽象Policy类的子类并实现getPermissions方法(需要时实现其它方法)。
通过将 "policy.provider"安全属性(在安全属性文件中)的值重新设置到所期望的Policy实现类名中,则缺省Policy实现可被改变。安全属性文件名称如下:
{java.home}/lib/security/java.security (Solaris)
{java.home}\lib\security\java.security (Windows)
这里,{java.home}指安装有JDK的目录。
属性policy.provider指定策略类的名称,其缺省值如下:
policy.provider=sun.security.provider.PolicyFile
为了用户化,你可以改变该属性的值以指定另一个类,如下所示:
policy.provider=com.mycom.MyPolicy
注意:MyPolicy类必须是java.security.Policy的一个子类。或许值得强调这样的策略类覆盖是暂时的解决方案,而更广泛的策略API将会替代这种方法。
? 3.4jjava.security.GeneralSecurityException
这是一个新的异常类,它是java.lang.Exception的一个子类。它试图表示,应该有两个与安全和安全包相关的异常类型。
java.lang.SecurityException和它的子类应该是运行时异常(未检查的,未声明的),它可能会引起一个程序的执行被中断。
只有在某种安全性违规被检测到时,这样的异常才被扔出。例如,当某些代码试图访问一个文件,但它没有访问许可,该异常就被扔出。应用程序开发人员可捕捉这些异常。
java.security.GeneralSecurityException是java.lang.Exception(必须被声明或捕捉)的一个子类,它在所有其它情况下被从安全包中扔出。
这样的异常与安全有关但不是致命的。例如,传输一个无效的密钥可能不是安全违规, 它应该被开发人员捕捉并处理。
在java.security包中还有两个异常,它们是RuntimeException的子类,由于向后兼容的要求,此时我们不能改变它。我们将在以后讨论这个问题。
..........|Next|..........
欢迎与我们联系:webmaster@prc.sun.com
版权所有 1997-1998 Sun(中国)公司,北京南礼士路66号建威大厦16层
All rights reserved.Legal Terms