探索java security manager
从setAccessble引入
今天看了点以前写的关于java 反射的代码,再次回忆起了一个当初就没弄太明白的一个问题,先看看引出问题的代码:
MyObject.java
public class MyObject {
public String name;
public int age;
public enum SEX {MALE, FEMALE}
private SEX sex;
public void sayHello(){
System.out.println("Hello World!");
}
}
public class MyObject {
public String name;
public int age;
public enum SEX {MALE, FEMALE}
private SEX sex;
public void sayHello(){
System.out.println("Hello World!");
}
}
Visitor.java
public class Visitor {
public static void main(String[] args){
Class<?> cls = MyObject.class;
//System.setSecurityManager(new SecurityManager());
System.out.println("Security Manager:"+System.getSecurityManager());
System.out.println(System.getProperty("java.security.manager"));
System.out.println(System.getProperty("user.dir"));
System.out.println(System.getProperty("java.home"));
try{
Constructor<?> constructor = cls.getConstructor(null);
Field nameField = cls.getDeclaredField("name");
Field sexField = cls.getDeclaredField("sex");
Object obj = constructor.newInstance();
String name = (String)nameField.get(obj);
//as to private member, we must access it by setting Without Java access control check
sexField.setAccessible(true);
Enum<?> sex = (Enum)sexField.get(obj);
System.out.println("initial properties:\r\n"
+ "name:"+name+"\r\n"
+ "sex:"+sex+"\r\n");
/*
* make some change for the properties of Object obj
*/
nameField.set(obj, "alan");
sexField.set(obj, SEX.MALE);
name = (String)nameField.get(obj);
sex = (Enum)sexField.get(obj);
System.out.println("properties after changed:\r\n"
+ "name:"+name+"\r\n"
+ "sex:"+sex+"\r\n");
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
public class Visitor {
public static void main(String[] args){
Class<?> cls = MyObject.class;
//System.setSecurityManager(new SecurityManager());
System.out.println("Security Manager:"+System.getSecurityManager());
System.out.println(System.getProperty("java.security.manager"));
System.out.println(System.getProperty("user.dir"));
System.out.println(System.getProperty("java.home"));
try{
Constructor<?> constructor = cls.getConstructor(null);
Field nameField = cls.getDeclaredField("name");
Field sexField = cls.getDeclaredField("sex");
Object obj = constructor.newInstance();
String name = (String)nameField.get(obj);
//as to private member, we must access it by setting Without Java access control check
sexField.setAccessible(true);
Enum<?> sex = (Enum)sexField.get(obj);
System.out.println("initial properties:\r\n"
+ "name:"+name+"\r\n"
+ "sex:"+sex+"\r\n");
/*
* make some change for the properties of Object obj
*/
nameField.set(obj, "alan");
sexField.set(obj, SEX.MALE);
name = (String)nameField.get(obj);
sex = (Enum)sexField.get(obj);
System.out.println("properties after changed:\r\n"
+ "name:"+name+"\r\n"
+ "sex:"+sex+"\r\n");
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
从上面的代码可以看出,使用java的反射机制时候使用了一个特殊的函数setAccessible(true),以前我们总是听别人说这个函数是让原本是private修饰的属性可以被访问和修改,那么底层到底是怎么实现的?
其实Field, Constructor, Method都继承了AccessibleObject,而这个类的setAccesssible函数会通过System.getSecurityManager获得一个安全管理器对象,一般情况下,SecurityManager(安全管理器)是没有载入的即为null,所以会进行下一步直接设置一个标志override,通过这个标志会可以取消属性隐藏。
有些博客经常说道这里就不说了,然后也提出了这样的反射机制会导致不安全的问题,因为本来是不可见不可访问的属性被访问了,但是下面要说的是,这并不是问题,通过SecurityManager可以做到不被随意访问,而是可以配置给特定的代码访问。
SecurityManager
思考到这里,我们可能需要理解一下SecurityManager,其实,安全管理器其实主要分为Policy和管理器两部分,而Policy是通过人工可以配置的,当前系统的默认配置在${java.home}/jre/lib/java.policy,而安全管理器主要是会从policy中获得一些叫做ProtectionDomain的对象,这些对象就相当于一个域的权限说明。
grant CodeBase "file:///home/alan/*"{ //permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.util.PropertyPermission "java.security.manager", "read"; //permission java.util.PropertyPermission "*", "read,write";};
上面的示例就是一个ProtectionDomain,可以简单理解为以grant开头的这样的一个权限配置信息就是一个ProtectionDomain.
上面的示例让路径在file:///home/alan/*指定的代码具有了获得系统属性中java.security.manager属性的权限。
SecurityManager 里面有很多checkXXX之类的函数,例如checkRead, checkWrite,checkPermission,而checkPermission一般很常用,在该处的setAccessible里面就调用了checkPermission。如果在运行该代码的时候是这样的:java -Djava.security.manager -cp xxx.jar com.xxx.Visitor,那么系统会载入默认的policy文件,而这个文件里面配置了java核心的api和ext里面的代码拥有所有权限,但是其他的用户代码只有基本的系统属性反问权限,这样就会抛出异常:
java -Djava.security.manager -cp test_sec.jar com.alan.test.security.y.Visitor
java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
at java.security.AccessController.checkPermission(AccessController.java:549)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:107)
at com.alan.test.security.y.Visitor.main(Visitor.java:25)
因此需要设置如下的ProtectionDomain
grant CodeBase "file:///home/alan/*"{ permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.util.PropertyPermission "*", "read,write";};
然后,使用java -Djava.security.manager -Djava.security.policy=/path/to/selfpolicy -cp xxx.jar com.xxx.Visitor
这样便大功告成。
ProtectionDomain的查找
ProtectionDomain是通过AccessController通过回溯堆栈中的调用者得到的,因此,ProtectionDomain的查找和check是自调用栈的栈顶一直到调用栈的栈底这样依次去判断是否具有权限。一般情况下,需要所有调用栈中的对应的保护域拥有权限才可以正常操作,否则抛出异常。
有的时候,调用栈较上层(更靠近栈顶)的代码可能希望执行一段代码,而这段代码在调用栈的较下层是不允许执行的。
为了使可信的代码执行较不可靠的代码操作(这段不可靠的代码位于调用栈的较下层且没有执行这个操作的权限),
AccessController类重载了四个名为doPrivileged()的静态方法。
当调用doPrivileged()方法时,就像调用其他任何方法一样,都会将一个新的栈帧压入栈。在由AccessController执行的栈检查中,一个>doPrivileged()方法调用的栈帧标识了检查过程的提前终止点。如果和调用doPrivileged()的方法相关联的保护域拥有执行被请求操作的权
限,AccessController将立即返回。这样这个操作就被允许,即使在栈下层的代码可能没有执行这个操作的权限