蹲厕所的熊 转载请注明原创出处,谢谢!
前言
上一篇文章我们说到了SecurityManager,它主要是通过配置策略文件来进行授权,最后的授权操作调用的是AccessController的checkPermission方法:
public void checkPermission(Permission perm) {
java.security.AccessController.checkPermission(perm);
}
看了一下API,我们发现AccessController有很多以doPrivileged开头的方法,查看了一下调用发现JDK很多源码都调用了它:
// 代码来自java.net.URLClassLoader
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
这种包装一层在内部使用run方法来执行操作的方式到底有什么作用呢?
Java中的安全模型
在 Java 中将执行程序分成本地和远程两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的 Java 实现中,安全依赖于沙箱 (Sandbox) 机制。沙箱机制就是将 Java 代码限定在虚拟机 (JVM) 特定的运行范围中,并且严格限制代码对本地系统的资源访问,通过这样的措施来保证对远程代码的有效隔离,防止对本地系统造成破坏。
但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。
在应用开发中存在很多关于安全的复杂用法,比如最常用的AccessController类的doPrivileged。该方法能够让执行的一段授权方法获得更大的权限(能让跟多的调用方也得到授权),也就是“特权”的含义,在一些特殊场景中非常有用。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。针对这种情况,Java SDK提供了doPrivileged方法来让程序突破当前域权限的限制,临时扩大访问权限。下面我们来讲解一下该方法如何使用。
使用
首先我们要在项目a中创建一个类 FileUtil
,用来提供特权创建文件方式和直接创建文件两种方式:
public class FileUtil {
// 工程 A 执行文件的路径
private final static String FOLDER_PATH = "/Users/Benjamin/Desktop";
public static void makeFile(String fileName) {
try {
// 尝试在工程 A 执行文件的路径中创建一个新文件
File fs = new File(FOLDER_PATH + "/" + fileName);
fs.createNewFile();
} catch (Exception e) {
// ignore
}
}
public static void doPrivilegedAction(final String fileName) {
// 用特权访问方式创建文件
AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
makeFile(fileName);
return null;
}
});
}
}
接着,我们把该项目打成一个jar包供其他项目使用,并把这个jar包放在 /Users/Benjamin/Desktop
下。然后改变默认策略文件的内容(这里我们使用Java自带的策略文件:${java.home}/jre/lib/security/java.policy)
// 授权工程 A 执行文件路径中文件在本目录中的写文件权限
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
// /-代表目录下所有目录的所有文件,/*仅仅代表目录下这一层级的所有文件
grant codebase "file:/Users/Benjamin/Desktop/-" {
permission java.io.FilePermission "/Users/Benjamin/Desktop/*", "write";
};
这段配置的意思是允许 a.jar
往 /Users/Benjamin/Desktop
这个目录下写文件。
接着,我们另起一个项目b,并引入jar包 a.jar
,调用 FileUtil
:
public class AccessControllerTest {
public static void main(String[] args) {
// 打开系统安全权限检查开关
System.setSecurityManager(new SecurityManager());
// 1. 用特权访问方式在工程 A 执行文件路径中创建 temp1.txt 文件
FileUtil.doPrivilegedAction("temp1.txt");
// 2. 用普通文件操作方式在工程 A 执行文件路径中创建 temp2.txt 文件
try {
File fs = new File("/Users/Benjamin/Desktop/temp2.txt");
fs.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
// 3. 直接调用普通接口方式在工程 A 执行文件路径中创建 temp3.txt 文件
FileUtil.makeFile("temp3.txt");
}
}
执行main方法,我们会发现桌面只生成了 temp1.txt
文件,而其他文件没有生成,错误信息为:
java.security.AccessControlException: access denied ("java.io.FilePermission" "/Users/Benjamin/Desktop/temp2.txt" "write")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.SecurityManager.checkWrite(SecurityManager.java:979)
at java.io.File.createNewFile(File.java:1008)
at com.github.security.AccessControllerTest.main(AccessControllerTest.java:28)
java.security.AccessControlException: access denied ("java.io.FilePermission" "/Users/Benjamin/Desktop/temp3.txt" "write")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.SecurityManager.checkWrite(SecurityManager.java:979)
at java.io.File.createNewFile(File.java:1008)
at com.github.web.FileUtil.makeFile(FileUtil.java:22)
at com.github.security.AccessControllerTest.main(AccessControllerTest.java:34)
也就是说,如果一个应用开启了安全管理,其他应用想要访问一些授权方法时,必须使用doPrivileged方法开启特权才行。
如果读完觉得有收获的话,欢迎点赞、关注、加公众号【蹲厕所的熊】,查阅更多精彩历史!!!




