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内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...
随机推荐
- 2021-7-7 VUE动态样式
Vue的动态样式实例1 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ...
- React: 路由重定向
解决方案 参考链接 https://v5.reactrouter.com/web/example/route-config
- 图解算法,原理逐步揭开「GitHub 热点速览」
想必每个面过大厂的小伙伴都被考过算法,那么有没有更快了解算法的方式呢?这是一个老项目,hello-algo 用图解的方式让你了解运行原理.此外,SQL 闯关自学项目也是一个让你能好好掌握 SQL 技术 ...
- 免费拥有自己的 Github 资源加速器
TurboHub 是一个免费的 Github 资源加速下载站点,可以帮助你快速下载 Github 上的资源.其核心逻辑是通过 Azure Static Web Apps 服务和 Azure Funct ...
- Linux字符界面安装更新VMware Tools
注:yeesn为我自己的用户名,实际操作中改用自己的用户名 1.切换到虚拟光驱目录 cd /media/yeesn/VMware Tools 2.复制压缩包到桌面 cp VMwareTools-xxx ...
- 2.go语言基础类型漫游
本篇前瞻 本篇是go语言的基础篇,主要是帮助大家梳理一下go语言的基本类型,注意本篇有参考go圣经,如果你有完整学习的需求可以看一下,另外,go语言的基本类型比较简单,介绍过程就比较粗暴. 基本类型 ...
- WPF-实现屏幕截图(一)
源码路径:https://gitee.com/LiuShuiRuoBing/wpf_screen_cut 实现功能 实现基本的截屏窗体 鼠标随意选择截图区域 鼠标抬起时弹出按钮区 快捷键Ctrl+Al ...
- torch.nn基础学习教程 | PyTorch nn Basic Tutorial
基于torch.nn搭建神经网络的基础教程大纲: 1. 引言 在我们开始深入探讨torch.nn之前,我们首先需要理解PyTorch及其神经网络库的基础知识.这一部分的内容将帮助你对PyTorch有一 ...
- 使用Skonsole自动生成Git提交信息
使用Skonsole自动生成Git提交信息 随着LLM应用的普及,日常工作中的很多使用都可以使用LLM来完成,比如Git提交信息的生成. Skonsole是一个基于Semantic Kernel的命令 ...
- 如何调用API接口获取淘宝商品数据
淘宝商品数据的获取是一项非常重要的技术,它可以为淘宝卖家和买家提供有利的数据分析和扩展市场的机会.调用API接口是一种快速.方便.高效的方式获取淘宝商品数据. 以下是一些步骤来调用API接口来获取淘宝 ...