您的位置:首页 > 编程语言 > Java开发

java 安全管理器详解(2)

2017-09-16 20:21 225 查看

一、运行时代码权限检测

由一个名为 java.lang.SecurityManager 的类负责监督类是否越权。在默认情况下,不会进行权限检测。

可通过两种方式开启权限检测:

在启动时传递给 JVM 的、名为 java.security.manager 的环境变量【-Djava.security.manager-Djava.security.policy=[策略文件路径]



动态设置SecurityManager (下面会举例说明这种方式)

开启权限检测后,任何代码都可以找到 SecurityManager 并调用它相应的 check 方法来检测是否越权。如果权限没有授予,那么将抛出一个 java.security.AccessControlException.

注:

在java1.1的时代, SecurityManager 通过其内部逻辑负责管理所有权限。 因此,任何需要权限检测的应用程序都必须实现并安装一个 SecurityManager。

Java 2 平台安全体系结构通过引入一个名为 AccessController 的新类使这一切变得简单了,并更具有可扩展性。这个类的目的与 SecurityManager 是一样的,即它负责做出访问决定。当然, 为了向后兼容性保留了 SecurityManager 类,但是其更新的实现委派给了底层的 AccessController。还是需要实现并安装

二、JDK核心代码的权限控制

JDK核心底层代码同样需要进行权限控制,对于不同的操作定义了具体的权限类,涉及内容包括:文件、套接字、网络、安全性、运行时、属性、AWT、反射和可序列化,对应的权限控制类为:

java.io.FilePermission、
java.net.SocketPermission、
java.net.NetPermission、
java.security.SecurityPermission、
java.lang.RuntimePermission、
java.util.PropertyPermission、
java.awt.AWTPermission、
java.lang.reflect.ReflectPermission
java.io.SerializablePermission


除前两个(FilePermission 和 SocketPermission)类以外的所有类都是 java.security.BasicPermission 的子类,而 java.security.BasicPermission 类又是顶级权限类 java.security.Permission 的抽象子类。

三、自定义java类加载器,并通过类加载器授予指定类权限

1.简单介绍类加载器

简单说,类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。如果站在JVM的角度来看,只存在两种类加载器:

启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:

扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。



2.类加载的顺序

通过观察 java.lang.ClassLoader.loadClass(String name)源码来了解类加载顺序

protected Class<?> loadClass(String name, boolean resolve )
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name )) {
// First, check if the class has already been loaded
Class c = findLoadedClass( name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent. loadClass( name, false );
} else {
c = findBootstrapClassOrNull(name );
}
} catch (ClassNotFoundException e ) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass( name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime( t1 - t0 );
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom( t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve ) {
resolveClass( c);
}
return c ;
}
}


类加载大致顺序如下(双亲委派模型):

首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。(findLoadedClass )

如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载

如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

观察 findClass方法

protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}


在上面介绍的类加载顺序中, loadClass在父加载器无法加载类的时候(未重写findClass方法),就会调用我们自定义的类加载器中的findeClass函数



3.自定义类加载器,并授予类加载器权限

继承java.lang.ClassLoader

重写findClass方法

public class MyClassLoader extends ClassLoader{

private String path;

public MyClassLoader(String path) {
this .path = path ;
}

@Override
protected Class<?> findClass(String name ) throws ClassNotFoundException {
byte [] b = null;
try {
b = getbyte( new File(path ));
} catch (Exception e ) {
e.printStackTrace();
}

PermissionCollection pc = new Permissions();
Permission p = new FilePermission("d:/demo.txt" ,"write" );
pc.add( p);
ProtectionDomain defaultDomain =
new ProtectionDomain( new CodeSource( null, (Certificate[]) null ),
pc, this , null );
return defineClass( null, b, 0 ,b .length , defaultDomain);
}

private byte [] getbyte(File file ) throws Exception{
FileInputStream in =new FileInputStream( file);
byte [] b = new byte[1024];
ByteArrayOutputStream out= new ByteArrayOutputStream();
int len =0;
while ((len =in .read(b ))!=-1){
out.write( b, 0, len);
}
out.close();
b= out.toByteArray();
return b ;
}
}

public class Test {

public void hello() {
System. out .println("hello world" );
}
}

public class App {

public static void main(String[] args) throws Exception {

MyClassLoader mcl = new MyClassLoader("D:/eclipse-workspaces/test/Test.class" );

Class clazz = mcl .loadClass( "Test");
Object obj = clazz.newInstance();

Method helloMethod = clazz .getDeclaredMethod( "hello", null) ;
helloMethod .invoke( obj, null) ;

System. out .println(clazz .getProtectionDomain ());
}
}




在上面的打印结果中,能清晰的看到权限:



由类加载器MyClassLoader加载的类,对于d:/demo.txt只有写的权限。

4.对象权限验证

我们通过 java.lang.SecurityManager 类来进行权限验证



运行时别忘了打开权限检测,默认是不开启的:



运行之后提示main方法没有创建类加载器的权限,这个可以暂时不理会,在下面会讲

Exception in thread "main" java.security.AccessControlException : access denied ("java.lang.RuntimePermission" "createClassLoader")
at java.security.AccessControlContext.checkPermission( AccessControlContext.java:366)
at java.security.AccessController.checkPermission( AccessController.java:560)
at java.lang.SecurityManager.checkPermission( SecurityManager.java:549)
at java.lang.SecurityManager.checkCreateClassLoader( SecurityManager.java:611)
at java.lang.ClassLoader.checkCreateClassLoader( ClassLoader.java:273)
at java.lang.ClassLoader.<init>(ClassLoader.java:334 )
at com.classloaddemo.MyClassLoader.<init>( MyClassLoader.java:18)
at com.classloaddemo.App.main( App.java:10 )


正常运行的情况下,应该会输出一下内容:

因为没有对d:/demo.text读的权限,所以会抛出AccessControlException

java.security.AccessControlException : access denied ("java.io.FilePermission" "D:\eclipse-workspaces\test\Test.class" "read")
at java.security.AccessControlContext.checkPermission( AccessControlContext.java:366)
at java.security.AccessController.checkPermission( AccessController.java:560)
at java.lang.SecurityManager.checkPermission( SecurityManager.java:549)
at java.lang.SecurityManager.checkRead(SecurityManager.java:888 )
at java.io.FileInputStream.<init>(FileInputStream.java:131 )
at com.classloaddemo.MyClassLoader.getbyte( MyClassLoader.java:44)
at com.classloaddemo.MyClassLoader.findClass( MyClassLoader.java:26)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423 )
at java.lang.ClassLoader.loadClass(ClassLoader.java:356 )
at com.classloaddemo.App.main( App.java:12 )


四、策略文件的使用

除了上面一种通过类加载的方式授予权限,java还可以通过配置文件进行授权,整个应用共同使用一个策略文件。

策略文件的语法可参考:http://blog.csdn.net/hudashi/article/details/7069764

上面遇到过的异常:

Exception in thread "main" java.security.AccessControlException : access denied ("java.lang.RuntimePermission" "createClassLoader")
at java.security.AccessControlContext.checkPermission( AccessControlContext.java:366)
at java.security.AccessController.checkPermission( AccessController.java:560)
at java.lang.SecurityManager.checkPermission( SecurityManager.java:549)
at java.lang.SecurityManager.checkCreateClassLoader( SecurityManager.java:611)
at java.lang.ClassLoader.checkCreateClassLoader( ClassLoader.java:273)
at java.lang.ClassLoader.<init>(ClassLoader.java:334 )
at com.classloaddemo.MyClassLoader.<init>( MyClassLoader.java:18)
at com.classloaddemo.App.main( App.java:10 )


我们可以指定策略文件,并在策略文件中增加以下配置来解决:

permission java.lang.RuntimePermission "createClassLoader";


在启动时指定策略文件的位置:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息