13.3.3 特权动作

在一个方法调用的执行过程中,实际的执行流程通常会跨越多个保护域的边界。比如在程序中输出一行文本到控制台,在执行过程中最终需要调用系统域中所提供的访问系统资源的方法,相当于对该方法的调用同时跨越了应用域和系统域。在进行权限检查时,不仅需要查看对该方法的调用在当前调用上下文中是否合法,还需要沿着方法调用栈逐个向上检查,确保对其中的每个方法的调用都是合法的。这些方法的调用过程形成一个完整的链条。如果在调用链上的任何一个方法不具有所需的权限,那么整个调用过程是非法的,会抛出AccessControlException异常。唯一例外的情况是,如果方法的调用执行的是特权动作(privileged action),那么就不受这个限制。在进行权限检查时,Java平台采用的是延迟判断的机制。在每次需要进行权限检查时,会根据当前调用上下文的即时状态来进行判断。

特权动作只关心是否具备动作本身所要求的权限,而并不关心调用者是谁。在沿着调用链向上进行权限检查的过程中,如果遇到了特权动作,则只检查该特权动作所要求的权限是否满足,而不再继续沿着调用链向上检查。对于一个写入文件的特权动作,它只要求对该文件具有写入权限即可,并不关心是谁要求它执行这样的动作。特权动作由java.security.PrivilegedAction接口和java.security.PrivilegedExceptionAction接口的实现类来表示。两个接口中都只有一个run方法用来执行具体的操作。不同之处在于PrivilegedAction接口的run方法不能抛出受检异常,而PrivilegedExceptionAction类的run方法是可以的。PrivilegedAction接口和PrivilegedExceptionAction接口实现类的对象由AccessController类的静态方法doPrivileged负责执行。

如果程序中需要读取系统属性,可以使用System类的getProperty方法。在getProperty方法的调用中会检查调用者是否具有相应的“java.util.PropertyPermission”权限。在启用了安全管理器之后,代码清单13-9中的get方法在调用时会抛出AccessControlException异常。

代码清单13-9 获取系统属性的方法示例


public static String get(String property){

return System.getProperty(property);

}


一种简单的做法是修改程序所用的策略,为代码清单13-9中的方法调用增加所需的权限。但是程序中类似的调用可能很多,不可能为所有这些方法的调用都逐一添加所需的权限。特权动作可以解决这个问题。在代码清单13-10中,把对System类的getProperty方法的调用封装在一个PrivilegedAction接口的实现中,并使用AccessController类的doPrivileged方法来执行。这样做的好处是,只要为代码清单13-10中的getWithPrivilege方法添加所需的“java.util.PropertyPermission”权限声明,程序中的其他部分即可直接调用该方法,不需要分配额外的权限。

代码清单13-10 使用特权动作获取系统属性


public static String getWithPrivilege(final String property){

return AccessController.doPrivileged(new PrivilegedAction<String>(){

public String run(){

return System.getProperty(property);

}

});

}


如果特权动作的执行抛出受检异常,那么需要使用PrivilegedExceptionAction接口的实现类。代码清单13-11给出了一个使用特权动作写入文件的示例。在进行文件写入操作的过程中可能会抛出受检异常IOException,因此在PrivilegedExceptionAction接口的实现类的run方法中声明了抛出IOException异常。AccessController类的doPrivileged方法在执行PrivilegedExceptionAction接口的实现对象时会抛出受检异常java.security.PrivilegedActionException,因此在代码中需要捕获该异常,并通过getCause方法得到在特权动作执行中产生的实际的IOException异常,并把该异常重新抛出。通过这样的方式,writeFileWithPrivilege方法的声明与代码清单13-8中的writeFile方法保持一致,对方法的使用者屏蔽了使用特权动作的实现细节。

代码清单13-11 使用特权动作写入文件的示例


public void writeFileWithPrivilege(final Path path, final byte[]content)throws

IOException{

try{

AccessController.doPrivileged(new PrivilegedExceptionAction<String>(){

public String run()throws IOException{

Files.write(path, content);

return path.toString();

}

});

}catch(PrivilegedActionException e){

throw(IOException)e.getCause();

}

}