Java默认不打开安全检查,如果不打开,本地程序拥有所有权限。
但是如果程序中加了
System.setSecurityManager(new SecurityManager());
则Java程序会检验权限。
假设有这样一种情况:A程序想在 C:\\Users\\taohuan\\Desktop\\test1 这个目录中新建一个文件,但是它没有相应的权限,但是它引用了另外一个Jar包B,刚好B有权限在C:\\Users\\taohuan\\Desktop\\test1目录中新建文件,还有更巧的是B在新建文件的时候采用的是AccessController.doPrivileged方法进行的,这种情况下,A就可以调用B的创建文件的方法进行创建文件了。
下面举一个例子。
新建一个B工程,名字为demo-core,所在路径为:D:\\Tao\\Project\\my-demo\\demo-parent\\demo-core,是一个简单的jar包程序,新建一个类PrivilegedFileUtil
package com.mydemo;
import java.io.File;
import java.io.IOException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* @author taohuan
*/
public class PrivilegedFileUtil {
public static boolean canRead(String fileName) {
try {
// 尝试普通方式创建一个新文件
File fs = new File(fileName);
return fs.canRead();
} catch (AccessControlException e) {
e.printStackTrace();
}
return false;
}
public static void makeFile(String fileName) {
try {
// 尝试普通方式创建一个新文件
File fs = new File(fileName);
fs.createNewFile();
} catch (AccessControlException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void doPrivilegedAction(final String fileName) {
// 用特权访问方式创建文件
AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
makeFile(fileName);
return null;
}
});
}
}
再新建一个另一个工程A,名字为demo-main,所在路径为:D:\\Tao\\Project\\my-demo\\demo-parent\\demo-main,并且引用demo-core工程,
<dependency>
<groupId>com.mydemo</groupId>
<artifactId>demo-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
新建一个类AccessControllerTest
package com.mydemo.test;
import com.mydemo.PrivilegedFileUtil;
import java.security.AccessControlException;
/**
* @author taohuan
*/
public class AccessControllerTest {
public static void main(String[] args) {
// 打开系统安全权限检查开关
System.setSecurityManager(new SecurityManager());
try {
String pathName = "C:\\Users\\taohuan\\Desktop\\test1\\test-path-product-1.txt";
System.out.println(PrivilegedFileUtil.canRead(pathName));
}catch (AccessControlException e1) {
e1.printStackTrace();
}
try {
String pathName = "C:\\Users\\taohuan\\Desktop\\test1\\test-wtite.txt";
PrivilegedFileUtil.makeFile(pathName);
} catch (AccessControlException e1) {
e1.printStackTrace();
}
try {
String pathName = "C:\\Users\\taohuan\\Desktop\\test1\\test-wtite-use-Privileged.txt";
PrivilegedFileUtil.doPrivilegedAction(pathName);
} catch (AccessControlException e1) {
e1.printStackTrace();
}
}
}
1、此时直接运行AccessControllerTest的main方法,发现会报错,没有权限
2、在demo-main工程的根路径下新建MyPolicy.txt文件
//授权D:\\Tao\\Project\\my-demo\\demo-parent\\demo-core\\target\\classes 下所有jar包或class类 可以读取和写入C:\\Users\\taohuan\\Desktop\\下所有文件
grant codebase "file:D:\\Tao\\Project\\my-demo\\demo-parent\\demo-core\\target\\classes"
{
permission java.io.FilePermission
"C:\\Users\\taohuan\\Desktop\\test1\\*", "read,write";
};
在运行AccessControllerTest时添加vm参数
-Djava.security.policy=D:/Tao/Project/my-demo/demo-parent/demo-main/MyPolicy.txt
此时在运行发现test-wtite-use-Privileged.txt文件能够创建出来。
可以看到test-wtite-use-Privileged.txt文件创建的时候采用的是
// 用特权访问方式创建文件
AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
makeFile(fileName);
return null;
}
});
之所以能够创建出来,因为PrivilegedFileUtil有写C:\\Users\\taohuan\\Desktop\\test1 目录的权限,并且采用了AccessController.doPrivileged方式。
这里还可以看到即使PrivilegedFileUtil有读C:\\Users\\taohuan\\Desktop\\test1 目录的权限,但是读的时候并没有采用AccessController.doPrivileged的方式,所以还是会判定为没有权限,抛了异常。
总结一下:
类java.security.AccessController提供了一个默认的安全策略执行机制,它使用栈检查来决定潜在不安全的操作是否被允许。
这个访问控制器不能被实例化,它不是一个对象,而是集合在单个类中的多个静态方法。
如果你安装了具体安全管理器,其实最终是由这个AccessController来决定一个潜在不安全的方法是否否被允许。
每一个栈帧代表了由当前线程调用的某个方法,每一个方法是在某个类中定义的,每一个类又属于某个保护域,每个保护域包含一些权限。
因此,每个栈帧间接地和一些权限相关。
AccessController 类用于与访问控制相关的操作和决定。
更确切地说,AccessController 类用于以下三个目的:
- 基于当前生效的安全策略决定是允许还是拒绝对关键系统资源的访问
- 将代码标记为享有“特权”,从而影响后续访问决定,以及
- 获取当前调用上下文的“快照”,这样便可以相对于已保存的上下文作出其他上下文的访问控制决定。
checkPermission 方法确定应该批准还是拒绝由指定权限所指示的访问请求。示例调用如下所示。在此例中,checkPermission
将确定是否批准对 "/temp" 目录中名为 "testFile" 的文件的“读”访问。
FilePermission perm = new FilePermission("/temp/testFile", "read"); AccessController.checkPermission(perm);
如果允许执行请求的访问,则 checkPermission
正常返回。如果拒绝,则抛出 AccessControlException。如果请求的权限类型不正确或包含无效值,也会抛出 AccessControlException。只要有可能,都会给出此类信息。假定当前线程按照调用方 1 到调用方 2 直到调用方 m 的顺序遍历了 m 个调用方。那么调用方 m 调用 checkPermission
方法。checkPermission
方法基于以下算法确定是批准还是拒绝进行访问:
i = m;
while (i > 0) {
if (caller i's domain does not have the permission)
throw AccessControlException
else if (caller i is marked as privileged) {
if (a context was specified in the call to doPrivileged)
context.checkPermission(permission)
return;
}
i = i - 1;
};
// Next, check the context inherited when
// the thread was created. Whenever a new thread is created, the
// AccessControlContext at that time is
// stored and associated with the new thread, as the "inherited"
// context.
inheritedContext.checkPermission(permission);
可以将调用方标记为享有“特权”。在做访问控制决定时,如果遇到通过调用不带上下文参数(请参阅下文,以获取关于上下文参数的信息)的 doPrivileged
标记为“特权”的调用方,则 checkPermission
方法将停止检查。如果该调用方的域具有指定的权限,则不进行进一步检查,并且 checkPermission
正常返回,指示允许所请求的访问。如果该域不具有指定的权限,则通常抛出异常。
在某一个线程的调用栈中,当 AccessController 的 checkPermission 方法被最近的调用程序调用时,对于程序要求的所有访问权限,ACC 决定是否授权的基本算法如下:
1. 如果调用链中的某个调用程序没有所需的权限,将抛出 AccessControlException;
2. 若是满足以下情况即被授予权限:
a. 调用程序访问另一个有该权限域里程序的方法,并且此方法标记为有访问“特权”;
b. 调用程序所调用(直接或间接)的后续对象都有上述权限。