今天在阅读 Thread 类的源码的时候,看到了如下一段代码:
SecurityManager security = System.getSecurityManager();
这个和 Security 有关的对象阻碍了我阅读的脚步,并且成功地吸引了我的注意。下面就来探讨一下这个类到底有什么样的魔力。
当运行未知的 Java 程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用 Java 安全管理器。
在 JDK 1.8 中默认的安全管理器配置文件是 $JAVA_HOME/jre/lib/security/java.policy
,即当未指定配置文件时,将会使用该配置。
安全管理器在 Java 语言中的作用就是检查操作是否有权限执行。是 Java 沙箱的基础组件。我们一般所说的打开沙箱,也是加 -Djava.security.manager
选项。
其实日常的很多 API 都涉及到安全管理器,它的工作原理一般是:
属性
public class SecurityManager {
// 进程中是否有安全检查
@Deprecated
protected boolean inCheck;
// 是否初始化
private boolean initialized = false;
// 线程组
private static ThreadGroup rootGroup = getRootGroup();
private static boolean packageAccessValid = false;
private static String[] packageAccess;
private static final Object packageAccessLock = new Object();
private static boolean packageDefinitionValid = false;
private static String[] packageDefinition;
private static final Object packageDefinitionLock = new Object();
}
SecurityManager 中属性比较少,主要是一些标志位。
方法
SecurityManager 中的方法除了获取一些信息外,剩下的基本都是 check 方法,分别囊括了文件的读写删除和执行、网络的连接和监听、线程的访问、以及其他包括打印机剪贴板等系统功能。而这些 check 代码也基本横叉到了所有的核心 Java API 上。正是这些方法的存在,使得我们的 Java 程序能够抵挡外来恶意代码的攻击,可以说 SecurityManager 就是 Java 语言的保护伞。
启动安全管理有两种方式,建议使用启动参数方式。
启动程序的时候通过附加参数启动安全管理器:
-Djava.security.manager
若要同时指定配置文件的位置那么示例如下:
-Djava.security.manager -Djava.security.policy="C:/java.policy"
也可以通过编码方式启动,不过不建议:
System.setSecurityManager(new SecurityManager());
通过参数启动,本质上也是通过编码启动,不过参数启动使用灵活,项目启动源码如下(sun.misc.Launcher):
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
安全管理器可以自定义,作为核心 API 调用的部分,我们可以自己为自己的业务定制安全管理逻辑。举个例子如下:
public class SecurityManagerTest {
static class MySecurityManager extends SecurityManager {
@Override
public void checkExit(int status) {
throw new SecurityException("no exit");
}
}
public static void main(String[] args) {
MySecurityManager sm = new MySecurityManager();
System.out.println(System.getSecurityManager());
System.setSecurityManager(sm); // 注释掉测一下
System.exit(0);
}
}
注释掉代码中的注释行,系统打印 null,然后正常退出。当我们打开注释,并且自己扩展一个 SecurityManager ——MySecurityManager,它做的事情很简单,就是覆盖了 checkExit() 方法,在系统退出时抛出一个“no exit”的异常。再执行,结果变成了
Exception in thread "main" java.lang.SecurityException: no exit
at com.ml.SecurityManagerTest$MySecurityManager.checkExit(SecurityManagerTest.java:11)
at java.lang.Runtime.exit(Runtime.java:107)
at java.lang.System.exit(System.java:971)
at com.ml.SecurityManagerTest.main(SecurityManagerTest.java:20)
null
显然,安全管理器生效了。
参考:
https://yq.aliyun.com/articles/57223?&utm_source=qq