本节书摘来异步社区《Java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.16节,作者:【美】Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达), Robert C.Seacord(罗伯特 C.西科德), Dean F.Sutherland(迪恩 F.萨瑟兰), David Svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。
Java安全策略为代码授予权限,用来允许指定代码访问特定的系统资源。一个被授予许可的代码源(CodeSource类型的对象),是由代码位置(URL)和证书引用组成的,该证书包含公钥以及与之对应的私钥,用来对代码进行数字签名, 代码只有在被某证书数字签名之后,才能关联到该证书引用。代码只有在被某证书数字签名之后,才能关联到该证书引用。保护域(protection domain)包含一个CodeSource对象,以及CodeSource中的代码被授予的权限,这是由当前生效的安全策略所决定的。因此,用相同的密钥来进行签名的、来自相同URL的类,会被放置在相同的保护域中。一个类仅仅属于一个保护域。具有相同权限、但来自不同代码源的类,属于不同的保护域。
每个Java类都运行在由代码源决定的恰当的保护域里。运行在安全管理器之下的任何代码,在执行任何安全相关的操作时,都必须被授予特定的权限,如读或者写一个文件时必须要有执行文件读或者写的权限。通过使用AccessController.doPrivileged()方法,特权代码可以代表无特权的调用者,访问特权资源,这是很有必要的。例如,当一个系统工具程序需要代表用户打开一个字体文件用来显示一个文档,但是应用程序本身缺乏权限做这样的事的时候。为了执行该操作,系统工具程序会使用它的全部特权来获取这个字体,而忽略调用者的特权。特权代码运行在与代码源相关的所有特权保护域里。这些特权往往超出了执行特权操作的需要。理想的情况下,代码应该被授予恰好满足其完成操作所需特权的最小集合。
指南19中描述了另外一种用来消除多余特权的方法。
下面的违规代码示例显示了一个库方法,通过使用包装器方法performActionOnFile()来允许调用者执行授权操作(读文件)。
private FileInputStream openFile() { final FileInputStream f[] = { null }; AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { f[0] = new FileInputStream("file"); } catch(FileNotFoundException fnf) { // Forward to handler } return null; } }); return f[0]; } // Wrapper method public void performActionOnFile() { try (FileInputStream f = openFile()){ // Perform operation } catch (Throwable t) { // Handle exception } }``` 在这个例子中,对可信代码授予的特权超出了读取一个文件的真实需要,即便是需要读取文件,也只需要为doPrivileged()代码块授权。因此,这段代码为代码块提供了多余的特权,从而违反了最小特权原则。 ####合规解决方案 双参数形式的doPrivileged()方法从调用者那里接受一个作为第二个参数传递的AccessControlContext对象,并将所包含代码的特权限制在保护域特权和上下文权限的交集中。因此,当调用者只希望授予代码读取文件权限时,可以提供一个只有文件读取权限的上下文。 一个被适当授予文件读取权限的AccessControlContext,可以作为一个内部类:private FileInputStream openFile(AccessControlContext context) { if (context == null) { throw new SecurityException("Missing AccessControlContext"); }
final FileInputStream f[] = { null }; AccessController.doPrivileged( new PrivilegedAction() { public Object run() { try { f[0] = new FileInputStream("file"); } catch (FileNotFoundException fnf) { // Forward to handler } return null; } }, // Restrict the privileges by passing the context argument context); return f[0];}
private static class FileAccessControlContext { public static final AccessControlContext INSTANCE; static { Permission perm = new java.io.FilePermission("file", "read"); PermissionCollection perms = perm.newPermissionCollection(); perms.add(perm); INSTANCE = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms)}); }}
// Wrapper methodpublic void performActionOnFile() { try { final FileInputStream f = // Grant only open-for-reading privileges openFile(FileAccessControlContext.INSTANCE); // Perform action } catch (Throwable t) { // Handle exception }}`如果调用者缺乏创建一个适当的AccessControlContext的权限,那么可以通过请求AccessController.getContext()来创建一个这样的实例。
未能遵循最小特权原则可能导致不可信、未授权的代码执行意想不到的特权操作。然而,过细地限制特权会增加程序复杂性。这些增加的复杂性和相应减少的可维护性必须同安全改进做出利弊权衡。