欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Online Judge】3.安全管理器

程序员文章站 2022-03-09 16:05:38
...

SecurityManager

先看说明书…JDK文档的解释如下:

安全管理器是一个允许应用程序实现安全策略的类。它允许应用程序在执行一个可能不安全或敏感的操作前确定该操作是什么,以及是否是在允许执行该操作的安全上下文中执行它。应用程序可以允许或不允许该操作。

SecurityManager 类包含了很多名称以单词 check 开头的方法。
Java 库中的各种方法在执行某些潜在的敏感操作前可以调用这些方法。
对 check 方法的典型调用如下: 

SecurityManager security = System.getSecurityManager();
if (security != null) {
    security.checkXXX(argument,  . . . );
}

checkPermission 方法使用一个上下文对象,以及根据该上下文而不是当前执行线程的上下文作出访问决策的权限。因此另一个上下文中的代码可以调用此方法,传递权限和以前保存的上下文对象。下面是一个示例调用,它使用了以前示例中获得的 SecurityManager sm

if (sm != null) sm.checkPermission(permission, context);

权限分为以下类别:文件、套接字、网络、安全性、运行时、属性、AWT、反射和可序列化。管理各种权限类别的类是 java.io.FilePermissionjava.net.SocketPermissionjava.net.NetPermissionjava.security.SecurityPermissionjava.lang.RuntimePermissionjava.util.PropertyPermissionjava.awt.AWTPermissionjava.lang.reflect.ReflectPermissionjava.io.SerializablePermission


有什么用?

一般来说我们写程序是用不到安全管理器的,因为我们只会执行我们自己写的代码,别人的?自己的时都没弄好还想着别人?

但是总会有特殊需求的时候,比如说早期Apple还很火的时候,在浏览器上你执行的是别人的代码、别人的 Java Apple小程序,你就得需要一个安全管理器,用于限制代码的执行权限。再比如说我们的OJ…


怎么用?

一般来说安全管理器默认是不开启的,我们要手动地开启,执行cmd命令 或下面的代码:

System.setSecurityManager(new SecurityManager());

这条代码会设置一个默认的安全管理器,具体的规则是写在一个文件里的,大概路径为:${java.home}/jre/security/java.policy

文件里面默认的内容是这样的:

// Standard extensions get all permissions by default

grant codeBase "file:${{java.ext.dirs}}/*" {
        permission java.security.AllPermission;
};


// default permissions granted to all domains

grant {
        // Allows any thread to stop itself using the java.lang.Thread.stop()
        // method that takes no argument.
        // Note that this permission is granted by default only to remain
        // backwards compatible.
        // It is strongly recommended that you either remove this permission
        // from this policy file or further restrict it to code sources
        // that you specify, because Thread.stop() is potentially unsafe.
        // See the API specification of java.lang.Thread.stop() for more
        // information.
        permission java.lang.RuntimePermission "stopThread";

        // allows anyone to listen on dynamic ports
        permission java.net.SocketPermission "localhost:0", "listen";

        // "standard" properies that can be read by anyone

        permission java.util.PropertyPermission "java.version", "read";
        permission java.util.PropertyPermission "java.vendor", "read";
        permission java.util.PropertyPermission "java.vendor.url", "read";
        permission java.util.PropertyPermission "java.class.version", "read";
        permission java.util.PropertyPermission "os.name", "read";
        permission java.util.PropertyPermission "os.version", "read";
        permission java.util.PropertyPermission "os.arch", "read";
        permission java.util.PropertyPermission "file.separator", "read";
        permission java.util.PropertyPermission "path.separator", "read";
        permission java.util.PropertyPermission "line.separator", "read";

        permission java.util.PropertyPermission "java.specification.version", "read";
        permission java.util.PropertyPermission "java.specification.vendor", "read";
        permission java.util.PropertyPermission "java.specification.name", "read";

        permission java.util.PropertyPermission "java.vm.specification.version", "read";
        permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
        permission java.util.PropertyPermission "java.vm.specification.name", "read";
        permission java.util.PropertyPermission "java.vm.version", "read";
        permission java.util.PropertyPermission "java.vm.vendor", "read";
        permission java.util.PropertyPermission "java.vm.name", "read";
};

很明显,这是个白名单,你允许什么就写什么上去。一开始我还蛮兴奋的,啪啪啪地敲上了如下的规则

grant codeBase "file:C:/-" {
        permission java.security.AllPermission;
};

grant codeBase "file:D:/-" {
        permission java.security.AllPermission;
};
grant codeBase "file:D:/Main.class" {
    	//permission java.security.AllPermission;
};

想法挺好的,不就是就是限制用户的class嘛。一测试,不错呦~果然把我写文件的操作给阻止了,然后删掉最后一个授权(8-10行),再测试,没有权限 ?!! 我电脑就两个盘,全部都弄成 AllPermission 了,居然还是抛出了 java.security.AccessControlException access denied ("java.io.FilePermission" "aaa" "write") 的异常。脑壳疼,上网搜了半天,看不懂,没有,不会,还是老老实实地写代码吧~

public static void main(String[] args) throws Exception {
		System.setSecurityManager(new SecurityManager());
		
		JavaJudge javaJudge = new JavaJudge("D:/","Main");
		String code="import java.io.File; public class Main{public static void main(String[] args) {System.out.println(\"hello\");File f= new File(\"aaa\");f.mkdir();}}";
		
		Class<?> clazz = javaJudge.getCodeClass(code);
		Method method = clazz.getMethod("main", String[].class);
		method.invoke(clazz, new Object[]{new String[]{"",}});		
}

【Online Judge】3.安全管理器

自定义 SecurityManager

在Java中欲想拓展某个类的功能,使用继承的方式即可。

其实可以在 SecurityManager 的源码中我们可以看到大多数的check方法都是间接地调用了 checkPermission 方法 ,例如:

public void checkExit(int status) {
	checkPermission(new RuntimePermission("exitVM."+status));
}

如果要控制执行的权限的话,我肯定会重写 checkPermission 方法 ,而不是重写几十个的check 方法,当然个别的权限另当别论

import java.io.FilePermission;
import java.lang.reflect.Method;
import java.lang.reflect.ReflectPermission;
import java.security.Permission;
import java.security.SecurityPermission;
import java.util.PropertyPermission;
import java.util.logging.LoggingPermission;

import 动态编译.JavaJudge;

public class MySecurityManager extends SecurityManager{

	@Override
	public void checkExit(int status) {
		throw new SecurityException("无法退出虚拟机");
        //exit属于 RuntimePermission 里面的权限, 但是很多时候我们都要用到runtime,故单独重写
    }
	
	@Override
	public void checkPermission(Permission perm) {
//      我们可以通过打印来查看具体的权限, 然后专门去定制
//		System.out.println(perm);
//		System.out.println(perm.getName()+" "+perm.getActions());
		perminssion(perm);
	}

	@Override
	public void checkPermission(Permission perm, Object context) {
		perminssion(perm);
	}
	
    // 自定义权限处理
	private void perminssion(Permission perm) {
		if (perm instanceof SecurityPermission) {
			if (perm.getName().startsWith("getProperty")) {
				return;
			}
		} else if (perm instanceof PropertyPermission) {
			if (perm.getActions().equals("read")) {
				return;
			}
		} else if (perm instanceof FilePermission) {
			if (perm.getActions().equals("read")) {
				return;
			}
		} else if (perm instanceof RuntimePermission
				|| perm instanceof ReflectPermission
				|| perm instanceof LoggingPermission) {
			return;
		}
		
		throw new SecurityException(perm.toString() + "无法使用该权限");
	}
}

抛出 SecurityException异常即代表该权限不被允许

注意: 当我们开启安全控制器时: System.setSecurityManager(new MySecurityManager());作用域是全局性的,而不单单是限制用户的代码,这也是我之前为什么纠结要修改java.policy文件的原因,如果能有一种方法能隔离权限那该多好啊~。其实也是有一个简单的解决方案的,不过对于并发并不友好:

System.setSecurityManager(new MySecurityManager());
run("用户的代码")
System.setSecurityManager(null);//解绑

简单粗暴