Java安全机制之一——SecurityManager和AccessController
前言:
在看socket相关代码的时候,AbstractPlainSocketImpl中的一段代码吸引了我,其实之前见过很多次类似的代码,但一直不想去看,只知道肯定和权限什么的相关,这次既然又碰到了就研究一下,毕竟也不能对java基本代码一无所知。
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
System.loadLibrary("net");
return null;
}
});
}
一些概念:
在jdk1.0的时代,applet依然是前端的一种可用的技术方案,比如可以嵌入在网页里运行。那个时候jdk的设计者们认为本地代码是安全的、远端代码是有风险的,而applet就是属于远端代码。因此,为了保证用户主机的安全和隐私,设计者参考了沙箱的思想,依托于当时jdk的体量很小,使用SecurityManager来分隔本地代码和远程代码,一个有权限,一个没有权限。
当时还出现了签名相关的机制(本文不关心,所以没做了解),随着java发展,1.1的时候出现了JAVABEAN、JDBC、反射等新概念,于是有了更多的新权限。设计者发现完全授予本地代码所有权限变得不合理,在1.2的时候重构了SecurityManager,变成了现在这样以最小粒度控制权限。这个时候的SecurityManager有两个功能,一是防御远程代码、二是防御本地代码的漏洞。
不知道是什么时候起,安全机制引入了域(ProtectDomain)的概念,也可以视作将一个大沙箱拆分为多个小沙箱。一个域对应一个沙箱,不同的代码(Codesource)被划分到不同域中,不同的域有着不同的权限(Permission),就像下图一样。同时可以给不同的域配置不同的权限,静态和动态均可,这个配置被称为策略(Policy)。

注意!
在JDK20和JDK21的security-guide中都提到了,和SecurityManager与之相关的api已被弃用,并将在未来的版本中删除。SecurityManager没有替代者。有关讨论和备选方案,请参阅JEP 411: Deprecate the Security Manager for Removal。
AccessController
AccessController主要有两个功能,对应的核心方法也是两类
checkPermission(校验是否存在权限)
public static void checkPermission(Permission perm)
throws AccessControlException
{
AccessControlContext stack = getStackAccessControlContext();
// if context is null, we had privileged system code on the stack.
//...其他获取context方法
AccessControlContext acc = stack.optimize();
acc.checkPermission(perm);
}
调用该方法时,一般会new一个期望的权限,然后作为入参传入checkPermission方法。
FilePermission perm = new FilePermission("C:\\Users\\Administrator\\Desktop\\liveController.txt", "read");
AccessController.checkPermission(perm);
注意,校验权限的时候会校验调用链路径上所有类的权限;假如调用链是从i开始,一直调用到m,校验逻辑如下
for (int i = m; i > 0; i--) {
if (caller i's domain does not have the permission)
throw AccessControlException
else if (caller i is marked as privileged) {
if (a context was specified in the call to doPrivileged)
context.checkPermission(permission)
if (limited permissions were specified in the call to doPrivileged) {
for (each limited permission) {
if (the limited permission implies the requested permission)
return;
}
} else
return;
}
}
代码执行的时候,每一次方法的调用都代表着一次入栈,而权限校验的时候则正好是从栈顶开始,依次判断每个栈帧是否具有权限,一直到栈底。

doPrivileged(临时授权)
public static native <T> T doPrivileged(PrivilegedAction<T> action);
这个方法的功能是将当前类所拥有的权限,能且仅能临时赋予其上游调用方。
在这个场景下,必然存在多个域,且只有某些域拥有权限A,但是其他域并没有这个权限。在java语言中很容易出现这个情况,比如我们调用一些第三方jar包的方法,三方jar包还能调用别的三方jar包,这种场景很有可能只有最底层的方法所对应的域拥有权限。此时为了方法的成功,就可以使用该方法。
使用的时候就是将代码逻辑放入AccessController.doPrivileged中即可,如下述代码一般。
//项目B,会打成security-demo.jar
public class PermissionDemo {
/**
* 使用特权访问机制
* @param file
*/
public void runWithOutPermission(String file){
AccessController.doPrivileged((PrivilegedAction<String>) () -> {
//hutool的FileUtil
String s = FileUtil.readString(file, "utf-8");
System.out.println(s);
return s;
});
}
}
//项目A,引入security-demo.jar
public class Aperson {
public static void main(String[] args) {
new PermissionDemo().runWithOutPermission("C:\\Users\\Administrator\\Desktop\\test.txt");
}
}
这里需要注意的是,AccessController.doPrivileged所在的当前类也需要拥有权限。以这个例子为例,文件读写是在hutool的FileUtil中执行,hutool对应的是域C;PermissionDemo对应的是域B,且会将自身权限向上传递;而Aperson对应的是域A。这个例子中,想要Aperson执行成功,必须是域C和域B都拥有test.txt的read权限。
对应的policy如下
grant codeBase "file:/C:/Users/Administrator/.m2/repository/cn/hutool/hutool-all/5.7.11/-"{
permission java.io.FilePermission "C:\\Users\\Administrator\\Desktop\\*", "read";
};
grant codeBase "file:/C:/Users/Administrator/.m2/repository/xxx/xxx/security-demo/-"{
permission java.io.FilePermission "C:\\Users\\Administrator\\Desktop\\*", "read";
};
从栈帧的角度来看的话,判断到doPrivilege对应的那层之后,校验就直接返回了,不校验下面层是否存在权限。

ProtectDomain
protectDomain类由codeSource和permission构成


CodeSource
类的来源,一般为jar包路径或者classpath路径(target/classes)
因为所有类在通过ClassLoader引入的,所以ClassLoader知道类的基本信息,在defineClass时,将CodeSource和Permission进行了绑定。同理,由于类必须通过ClassLoader加载,对于使用自定义ClassLoader加载的类,就只有那个类加载器知道对应的CodeSource和permission。因此,不同的类加载器本身就属于不同的域。
Permission
Java抽象出的顶层的类,核心方法是implies,该方法用来判断当前线程是否隐含指定权限,由各自的子类实现。子类实现过多,这里就不列举了。
PermissionCollection本质是个list,里面是某一类权限的多个实例,比如文件夹A-读权限,文件夹B-写权限,文件夹C-读写权限。
Permissions核心是一个map,key是Permissoin子类,value是PermissionCollection
SecurityManager
SecurityManage里有一堆check方法,调用的是AccessController.checkPermission方法,入参就是Permission各个子类的实例化。
开启方式:
隐性:启动时添加-Djava.security.manager
显性:System.setSecurityManager
public class NoShowTest {
static class CustomManager extends SecurityManager{
@Override
public void checkRead(String file) {
throw new AccessControlException("无权限访问");
}
}
public static void main(String[] args) {
System.setSecurityManager(new CustomManager());
System.getSecurityManager().checkRead("C:\\Users\\Administrator\\Desktop\\liveController.txt");
}
}
Policy
启动时通过 -Djava.security.policy=xxxx\custom.policy,如果没有指定,则默认使用jdk路径下\jre\lib\security\java.policy
参考:
Java安全:SecurityManager与AccessController - 掘金
Java沙箱机制的实现——安全管理器、访问控制器 - 掘金
第21章-再谈类的加载器
https://openjdk.org/jeps/411
https://docs.oracle.com/en/java/javase/20/security/java-security-overview1.html#GUID-BBEC2DC8-BA00-42B1-B52A-A49488FCF8FE
AccessController.doPrivileged - 山河已无恙 - 博客园
Java安全机制之一——SecurityManager和AccessController的更多相关文章
- JDK源码解析之Java SPI机制
1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...
- Java类加载机制的理解
算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解.要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机 ...
- 组件化框架设计之Java SPI机制(三)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将从深入理解java SPI机制来介绍组件化框架设计: ...
- Java SPI机制详解
Java SPI机制详解 1.什么是SPI? SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.SPI是一种动态替换发现的机制, 比如有个 ...
- 第28章 java反射机制
java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...
- Java反射机制
Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射: 静态编译:在编译时确定类型,绑定对象,即通过 ...
- java基础知识(十一)java反射机制(上)
java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...
- java基础知识(十一)java反射机制(下)
1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...
- JAVA 异常处理机制
主要讲述几点: 一.异常的简介 二.异常处理流程 三.运行时异常和非运行时异常 四.throws和throw关键字 一.异常简介 异常处理是在程序运行之中出现的情况,例如除数为零.异常类(Except ...
- java基础知识(四)java内存机制
Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...
随机推荐
- 运维自动化工具--Ansible
运维自动化工具Ansible 1. ansible安装 rocky安装 需要先安装 enel源 # yum install -y epel-release 然后再安装ansible # yum ins ...
- 通过Maxwell同步mariadb数据至kafka
实验环境 本地虚拟机 maraidb 10.8.8 kafka 2.12-3.3.1 maxwell由容器部署 1 mariadb 1.1 配置log_bin 配置文件中加入如下内容 server-i ...
- Java并发篇:6个必备的Java并发面试种子题目
线程创建和生命周期 线程的创建和生命周期涉及到线程的产生.执行和结束过程.让我们继续深入探索这个主题: 线程的创建方式有多种,你可以选择适合你场景的方式: 继承Thread类: 创建一个类,继承自Th ...
- 学习 YAML 语法
符号 意义 备注 - 表示数组 数组也叫序列 # 表示注释 只支持单行注释 空格缩进 表示层级关系 相同层级左侧必须对齐 --- 表示一份内容的开始 ... 表示一份内容的结束 可省略 : 表示键值对 ...
- 2021-7-12 VUE的增删改查功能简单运用
Vue增删改查简易实例 <!DOCTYPE html> <html> <head> <title> </title> <style t ...
- 安装.NET Framework4.5以上版本受阻怎么办?
安装和卸载 .NET Framework 受阻疑难解答 - .NET Framework | Microsoft Learn Windows RT 8.1.Windows 8.1 和 Windows ...
- 本地连接阿里云上的mysql centos
首先写下原因: 未让3306端口通过防火墙 1. 检查端口是否被防火墙挡住 telnet ip地址 3306 在windows中打开telnet应用, 参考:https://www.cnblogs. ...
- 2023牛客暑期多校训练营5 ABCDEGHI
比赛链接 A 题解 知识点:莫队,树状数组. 区间询问显然可以离线莫队,考虑端点移动对答案的影响. 不妨先考虑右端点右移一个位置,对答案的改变.假设右端点右移后在 \(r\) ,我们先要知道 \([l ...
- 应用性能监控工具(pinpoint)部署
Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控.方法执行详情查看.应用状态信息监控等功能.pinpoint使用HBASE储存数据. 下面介绍pinpoint部署及应用. 1. 安装 ...
- 知识图谱(Knowledge Graph)- Neo4j 5.10.0 使用 - Java SpringBoot 操作 Neo4j
上一篇使用了 CQL 实现了太极拳传承谱,这次使用JAVA SpringBoot 实现,只演示获取信息,源码连接在文章最后 三要素 在知识图谱中,通过三元组 <实体 × 关系 × 属性> ...