在第一篇文章,Java 代理学习笔记 —— 从反射开始里曾经提到过,在不启用Security Manager的情况下,我们可以直接通过反射来改变字段是否可以获取,再通过反射得到私有变量的值。那么,Security Manager是什么呢?对反射又有什么影响呢?
这篇文章将会介绍Java中的Security Manager。
在Orcale关于Security Manager的文档中,有如下描述
A security manager is an object that defines a security policy for an application. This policy specifies actions that are unsafe or sensitive. Any actions not allowed by the security policy cause a SecurityException to be thrown. An application can also query its security manager to discover which actions are allowed.
从上面可以知道,Security manager定义了应用的安全策略。在这些定义好的安全策略中,我们声明哪些操作是不安全或者是敏感的。所有违反了安全策略的操作都将会导致安全异常的抛出。
在java程序中,Security manager默认是不会被启用的,要启用Security Manager,我们需要增加如下的Java虚拟机参数。
-Djava.security.manager
还是回到第一篇文章的示例代码中
package reflectionSample;
import java.lang.reflect.*;
public class ReflectionMain {
public static void main(String args[]) {
try {
Class> clazz = Class.forName("reflectionSample.User");
Field nameField = clazz.getDeclaredField("name");
//获取name这个变量的类型全称
System.out.println(nameField.getType().toGenericString());//public final class java.lang.String
//获取name变量的声明全称
System.out.println(nameField.toGenericString()); //private java.lang.String reflectionSample.User.name
//设置User中name的值
User u = new User();
nameField.setAccessible(true);
nameField.set(u, "testName");
System.out.println(nameField.get(u)); //testName
} catch (Exception e) {
e.printStackTrace();
}
}
}
当我们增加虚拟机参数后,会抛出如下异常
java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:457)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128)
at reflectionSample.ReflectionMain.main(ReflectionMain.java:22)
这是由于默认的Security Manager启用的是白名单机制,也就是说,所有没有赋予权限的操作,都会导致异常的抛出。
那么,默认的配置文件,又在哪里呢?
%JAVA_HOME%\jre\lib\security\java.security
其中有两行非常重要的
# The default is to have a single system-wide policy file,
# and a policy file in the user's home directory.
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy
以上,就指明了系统默认的policy文件读取的地方。
我们再打开java.policy
,可以看到里面有如下的赋予权限的语句
grant codeBase "file:${java.home}/../lib/jvmx.jar" {
permission java.security.AllPermission;
};
policy文件就是由这些授权项组成,授权项的语法规则如下所示:
grant signedBy "signer_names", codeBase "URL" {
permission "permission_class_name" "target_name", "action",
signedBy "signer_names";
....
permission "permission_class_name" "target_name", "action",
signedBy "signer_names";
};
字段名称 | 字段作用 | 是否必须 | 备注 |
---|---|---|---|
signedBy | 表示使用的证书别名 | 否 | 若没有,则不会进行密钥验证 |
codeBase | 代码的原位置 | 否 | 若没有,则默认授权项会应用到所有文件 |
permission_class_name | 授权项具体类名 | 是 | |
target_name | 授权项的具体内容 | 否 | 由具体授权类决定 |
action | 被授权的操作 | 不一定 | 需要根据具体的授权类以及授权内容决定 |
而对于codeBase,又有相应的语法规则 (示例: codeBase “file:/C:/java/” )
- 属性扩展 可支持 ${user.dir}等变量读取 如 codeBase “file:${user.dir}” ,但只支持单层属性扩展,不支持多层属性扩展。 如 codeBase “file:${user.dir ${dir}}是不会生效的。
- 支持模糊匹配 通过 * 匹配目录下所有文件(不包含子目录) 通过 - 匹配目录下所有文件以及子目录中所有文件
知道了这些授权项的存在以及写法之后,我们就可以自定义授权项以及policy文件啦。
通过增加JVM参数-Djava.security.policy=C:/etc/java.policy
来指定自定义的java policy文件。如果想要查看security相关的log,可以增加JVM参数-Djava.security.debug=all
。
自定义的java.policy
grant codeBase "file:${user.dir}/-"{
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};
为文件增加了权限,就能够顺利执行啦~
说到Security Manager的工作机制,最重要的就是Security Manager里面的checkPermission
方法,我们通过传入不同的Permission项目,来进行检查。
在检查的时候,我们会具体地调用AccessController
里面的getStackAccessControlContext
方法,这个native方法将会检查当前的调用栈中有无违反权限的操作,如果有,就会抛出异常。
在Java原生的代码中,有如下的Permission继承关系
再列举一些常用的Permission项目以及他们的可选值
Permission项目 | target name | action |
---|---|---|
java.io.FilePermission | 文件目录 | read, write, execute, delete |
java.net.SocketPermission | 主机名或者ip地址 | accept, connect, listen, resolve |
java.util.PropertyPermission | 具体系统属性 | read, write |
java.lang.reflect.ReflectPermission | suppressAccessChecks | N/A |
java.security.AllPermission | N/A | N/A |
java.lang.RuntimePermission | exitVM 等 | N/A |
要查看更加详细的Permission项目,可以直接到Oracale api文档中查询