三 许可和安全规则
3.1 Permission类
许可(permission)类代表了对系统资源的访问权限。java.security.Permission是一个抽象类,并通过恰当的继承以代表特定访问权限。作为许可的一个例子,下面的代码用来创建一个对于/tmp目录下的abc文件的读的许可:
perm = new java.io.FilePermission("/tmp/abc", "read");
新许可可以继承自Permission或它的任一子类,如java.security.BasicPermission。子许可类(不是BasicPermission)一般属于他们自己的包。例如FilePermission就属于java.io包。每一个新的许可类都必须实现的一个至关重要的抽象方法就是implies方法。基本上,“a implies b”意味着如果得到了许可“a”,那它自然会得到许可“b”。这在做访问控制决定时是很重要的。
与抽象类java.security.Permission相关联的是抽象类java.security.PermissionCollection以及final类java.security.Permissions。
java.security.PermissionCollection代表了属于单一类型(例如文件许可)的许可对象的一个集合,方便归类。在许可能以任何顺序添加到PermissionCollection的案例里,例如文件许可,PermissionCollection对象确保当调用implies功能时语义保持正确是至关重要的。
java.security.Permissions代表了一个许可对象集合的集合,换句话说,是一个各单一类型许可集合的总集合。
应用可以自由的添加系统支持的新许可种类。如何添加这样的应用特定的许可将在后面讨论。
现在我们开始讨论所有内置许可的语法和语义。
3.1.1 java.security.Permission
这个抽象类是所有许可类的祖先。它定义了对所有许可都必要的功能。每一个许可实例通常都要传递一些String参数给构造函数来创建。在一个需要两个参数的非常普遍的情况下,第一个参数经常是“目标(target)的名字”(例如该许可所瞄准的文件的名字),第二个参数是动作(action)(例如对一个文件的“read”动作)。一般地,一组动作可以通过逗号分割的字符串来同时指定。
3.1.2 java.security.PermissionCollection
本类持有一个具有单一类型许可的集合。换句话说,该类的每一个实例都只持有相同类型的一组许可。3.1.3 java.security.Permissions
本类被设计用来持有各种各样类型的许可。从根本上说,它是一个java.security.PermissionCollection对象的集合。3.1.4 java.security.UnresolvedPermission
回顾一下,一个安全规则(policy)的内部状态通常是用关联在各代码源(code source)上的许可对象来表达的。然而考虑到Java技术的动态性,很有可能当规则已经初始化完毕但是某特定许可类所关联的实际代码在Java应用环境中还没有被加载和定义。例如,一个规则中配置的许可类可能在一个JAR包中,而该JAR包将在稍后被加载。UnresolvedPermission类用来持有这种“尚未解析的”许可。相似地,java.security.UnresolvedPermissionCollection保存了一个UnresolvedPermission许可的集合。
在对涉及到先前未解析类的许可的访问控制检查过程中,当发现该类已经被解析加载了,则该未解析许可就会被“解析”,并且合适的访问控制决定会被做出。就是,如果可能的话,一个合适的Permission类型将会基于UnresolvedPermission中的信息被实例化。这个新建的对象将会取代被删除的UnresolvedPermission。
如果检查过后许可仍然处于未解析状态,这个许可就被认为是无效的,就像安全规则从未提供过这条许可。
3.1.5 java.io.FilePermission
本类的目标(targets)可以通过以下的方式指定,其中目录和文件名是不能包含空格的字符串。
file
directory (same as directory/)
directory/file
directory/* (all files in this directory)
* (all files in the current directory)
directory/- (all files in the file system under this directory)
- (all files in the file system under the current directory)
"<<ALL FILES>>" (all files in the file system)
注意"<<ALL FILES>>"是一个代表本系统上所有文件的特殊字符串。在一个Solaris, Linux, 或者Mac OS X系统上,它包括了根目录下的所有文件。在一个MS-DOS系统上,它包括了所有驱动上的所有文件。动作(actions)包括:read,write,delete和execute。因此,下面是创建文件许可的有效示例代码:
import 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.31", "execute")。
注意:大部分这些字符串的格式是平台相关的。例如,在Windows系统中代表对C盘“temp”目录下的“foo”文件有读访问权限,你可以使用
FilePermission p = new FilePermission("c:\\temp\\foo", "read");
必须使用双反斜杠来代表一个反斜杠,因为字符串会被一个java.io.StreamTokenizer标记器处理,该标记器使用“\”作为一个转义符(例如:“\n”代表换行)因此要求两个反斜杠来表示一个反斜杠。在标记器处理完上述FilePermission的目标字符串,将双反斜杠转换为单反斜杠以后,实际路径的最终结果是
“c:\temp\foo”
在平台无关的统一文件描述语言出现以前,目标文件字符串都需要用一种平台相关的格式给出。同样注意,像“*”和“-”等元符号阻止了具体文件名称的使用。我们认为这是一个当前可以容忍的小局限。最后,注意“/-”和"<<ALL FILES>>"在Solaris, Linux, 和Mac OS X系统上代表了一样的目标,他们都代表了整个文件系统。(他们可以代表多个文件系统,如果这些文件系统都是可用的话)。这两个目标在其他操作系统上可能是不同的,例如在MS Windows和MacOS上。
还要注意,当一个目标的名称指定的只是一个目录,带着一个“read”动作,就像
FilePermission p = new FilePermission("/home/gong/", "read");
意味着你只是拥有了在该目录下list文件的许可,不能读其中的任何一个。想要得到对文件的read权限,你必须要么指定一个明确的文件名,要么使用“*”或“-”,就像
FilePermission p = new FilePermission("/home/gong/myfile", "read");
FilePermission p = new FilePermission("/home/gong/*", "read");
FilePermission p = new FilePermission("/home/gong/-", "read");
最后,注意代码总是自动对它所在位置及子位置中的文件拥有读的许可;不需要显式的许可。
3.1.6 java.net.SocketPermission
本类代表了通过sockets对一个网络资源的访问许可。本类的目标可以通过“hostname:port_range”来给出,其中的hostname可以通过以下方式给出:
hostname (a single host)
IP address (a single host)
localhost (the local machine)
"" (equivalent to "localhost")
hostname.domain (a single host within the domain)
hostname.subdomain.domain
*.domain (all hosts in the domain)
*.subdomain.domain
* (all hosts)
也就是说,host可以表达成一个DNS域名,一个数字的IP地址,“localhost”(对于本机)或者“”(与“localhost”相同)。
通配符“*”可以在DNS名称规则中包含一次。如果被包含,它必须在名字的最左侧,例如“*.sun.com”。
port_range可以以下列方式给出:
N (a single port)
N- (all ports numbered N and above)
-N (all ports numbered N and below)
N1-N2 (all ports between N1 and N2, inclusive)
在这里N,N1,和N2是从0到65535(2^16-1)的非负数。
针对sockets的动作包括accept, connect, listen, 和resolve(从根本上说是DNS查找)。注意到动作“resolve”被“accept”,“connect”,和“listen”所隐含--例如,那些可以监听或者可以接受连接或者发起对某host的连接的socket应当能够查找远程主机的名字。
下面是一些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
BasicPermission类继承自Permission类。它可被用于作为一些许可的基类,这些许可希望能遵循与BasicPermission相同的命名惯例。(看下面)一个BasicPermission的名字就是一个给定的许可的名字(例如,"exitVM",“setFactory”,“queuePrintJob”,等等)。其命名规范遵循层次属性的命名规范。一个星号可能出现在名字的最后,紧跟着一个“.”,或者就它自己,来指定一个通配符。例如:“java.*”或者“*”是有效的,“*java”或者"a*b"是无效的。
动作(action)字符串(继承自Permission)没有用到。那是因为,BasicPermission通常是用来作为“命名”权限的基类(命名权限包含了一个名字,但是没有动作列表;你要么拥有这个命名权限要么没有)。如果希望的话,子类也可以在BasicPermission之上实现动作(action)。
BasicPermission的一些子类包括java.lang.RuntimePermission, java.security.SecurityPermission, java.util.PropertyPermission, 和java.net.NetPermission。
3.1.8 java.util.PropertyPermission
本类的目标(target)基本上是定义在各种属性文件中的Java属性的名称。例如属性“java.home”和“os.name”。目标可以被指定为“*”(任意属性),“a.*”(名字以“a.”开头的任意属性),“a.b.*”,等等。注意到通配符只能出现一次并且只能出现在最右侧的位置。这是一个BasicPermission的子类,并在之上实现了动作。其动作包括read和write。他们的意思如下:“read”许可允许调用java.lang.System的getProperty方法来得到该属性的值,“write”许可允许调用setProperty方法来设置该参数的值。
3.1.9 java.lang.RuntimePermission
一个RuntimePermission的目标(target)可以表达为任何字符串,并且没有动作关联到目标上。例如,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非常相似;是一个没有动作的许可。该类的目标(target)可以是:
accessClipboard
accessEventQueue
listenToAllAWTEvents
showWindowWithoutWarningBanner
3.1.11 java.net.NetPermission
本类包含了以下的目标(target)并且没有动作:requestPasswordAuthentication
setDefaultAuthenticator
specifyStreamHandler
3.1.12 java.lang.reflect.ReflectPermission
这是为反射操作服务的许可类。一个ReflectPermission是一个命名许可(就像RuntimePermission)并且没有动作。当前唯一定义的名字为
suppressAccessChecks
该许可允许在使用反射得到的对象时阻止其执行标准Java编程语言的访问权限检查--针对public,default(package),access,protected,和private属性字段。
3.1.13 java.io.SerializablePermission
本类包含如下的目标(target)并且没有动作:enableSubclassImplementation
enableSubstitution
3.1.14 java.security.SecurityPermission
SecurityPermissions控制着对于安全相关对象的访问权限,例如Security, Policy, Provider, Signer, 以及Identity对象。该类包含如下的目标(target)并且没有动作: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同样包括可能在未来定义的新许可。在考虑赋予本许可的时候一定要非常小心。
3.1.16 javax.security.auth.AuthPermsision
AuthPermission处理身份认证许可及相关对象例如Subject,SubjectDomainCombiner,LoginContext,和Configuration。本类包含如下的目标(target)并且没有动作:doAs
doAsPrivileged
getSubject
getSubjectFromDomainCombiner
setReadOnly
modifyPrincipals
modifyPublicCredentials
modifyPrivateCredentials
refreshCredential
destroyCredential
createLoginContext.{name}
getLoginConfiguration
setLoginConfiguration
refreshLoginConfiguration
3.1.17 关于Permission含义的讨论
回想一下,许可经常被用来相互对比,为了使这样的对比更加容易,我们要求每一个许可类都定义一个implies方法,该方法代表了一个特定的许可类与其他的许可类是一个什么关系。例如, java.io.FilePermission("/tmp/*", "read")隐含了java.io.FilePermission("/tmp/a.txt", "read")但是无法隐含任何的java.net.NetPermission。对一部分读者而言,还有另外一层隐含也许不能立刻明显的表现出来。假设一个applet被赋予对整个文件系统的写的许可。由此可推测,该applet被允许替换掉系统二进制文件,包括JVM运行时环境。这实际上意味着该applet获得了所有的许可。
另外一个例子是,如果一个applet被赋予创建类加载器的运行时许可,这意味着它被赋予比这个许可多得多的许可,因为一个类加载器可以执行很多敏感的操作。
其他一旦赋予出去就会变的“危险”的许可包括可以设置系统属性的许可,定义package的运行时许可,以及加载本地(native)代码库的许可(因为Java安全体系并不是为本地代码层次设计的,它也不会阻止在这个层次的恶意行为),当然还有AllPermission。
如果想要更多的关于许可的信息,包括对分发特定的许可导致的风险的列表,还有所有的Java 2 SDK内置的需要许可的方法的列表,请查看http://java.sun.com/j2se/sdk/1.2/docs/guide/security/permissions.html 。
3.1.18 如何创建新类型的许可
除了Sun的微系统,没有其他任何人应该扩展内置在Java 2 SDK中的许可体系,这件事是很重要的。它维护了系统的一致性。上述扩展指的要么是添加新的功能,要么是为一个像java.lang.RuntimePermission这样的类引入额外的目标关键字。为了创建一个新的许可,推荐使用下面的步骤,用一个例子来展示。假设一个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
任何一个想要将这个新许可应用到特定代码的用户都可以通过在policy文件中增加一个入口记录(entry)来达到目的。(policy文件的语法细节将会在后面章节给出。)一个给予来自http://java.sun.com/的代码可观看5频道许可的policy文件记录的例子是:
grant codeBase "http://java.sun.com/" {
permission com.abc.TVPermission "channel-5", "watch";
}
在应用的资源管理代码中,当检查一个许可是否被允许时,调用accessController的checkPermission方法,并使用一个com.abc.TVPermission的对象作为参数。
com.abc.TVPermission tvperm = new com.abc.TVPermission("channel-5", "watch");
AccessController.checkPermission(tvperm);
注意,当添加一个新许可时,我们应该创建一个新(许可)类而不是为安全管理器增加一个新方法。(在过去,为了能够支持检查新的访问权限类型,你需要为SecurityManager类增加一个新方法。)
如果更多详细的如“channel-1:13”或“channel-*”这样的TVPermissions被允许,那可能有必要实现一个TVPermissionCollection对象,该对象需要知道如何处理这些名字的语义。
新写的代码应该总是通过调用AccessController的checkPermission方法来调用许可检查,这样就可以利用上内置的安全控制算法。没必要检查是否存在一个ClassLoader或SecurityManager。另一方面,如果安全控制算法应该交给已注册的安全管理器类(SecurityManager),那么应该调用相应的SecurityManager.checkPermission方法。
===========================================================================
本技术文档的翻译工作由不动明王1984独自完成,特此声明。
翻译辛苦,珍惜劳动,引用时请注明出处!
===========================================================================
上一篇:Java Security Architecture--Java安全体系技术文档翻译(一)
下一篇:Java Security Architecture--Java安全体系技术文档翻译(三)