前言:

在看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的更多相关文章

  1. JDK源码解析之Java SPI机制

    1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...

  2. Java类加载机制的理解

    算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解.要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机 ...

  3. 组件化框架设计之Java SPI机制(三)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将从深入理解java SPI机制来介绍组件化框架设计: ...

  4. Java SPI机制详解

    Java SPI机制详解 1.什么是SPI? SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.SPI是一种动态替换发现的机制, 比如有个 ...

  5. 第28章 java反射机制

    java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...

  6. Java反射机制

    Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射:     静态编译:在编译时确定类型,绑定对象,即通过 ...

  7. java基础知识(十一)java反射机制(上)

    java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...

  8. java基础知识(十一)java反射机制(下)

    1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...

  9. JAVA 异常处理机制

    主要讲述几点: 一.异常的简介 二.异常处理流程 三.运行时异常和非运行时异常 四.throws和throw关键字 一.异常简介 异常处理是在程序运行之中出现的情况,例如除数为零.异常类(Except ...

  10. java基础知识(四)java内存机制

    Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...

随机推荐

  1. 用Claude-2-100K复刻了胡锡进老师的写作风格

    大家好,我是老章 最近玩心大发,用Claude-2-100K复刻了胡锡进老师的写作风格,然后用这个风格点评世间万物. 蛮有意思的,直接看效果吧: 怎么实现的呢?老章不喜欢废话,极简介绍一下步骤. 第一 ...

  2. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(10.A)- FlexSPI NAND启动时间(RT1170)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RT1170 FlexSPI NAND启动时间. 本篇是 i.MXRT1170 启动时间评测第四弹,前三篇分别给大家评测 ...

  3. 调试linux内核(1): 环境准备和原理介绍

    开篇 现在流行的开源项目经历了长时间的开发, 积累了大量的代码, 想要一行一行地阅读代码去学习开源项目, 需要的时间成本是巨大的. 所以, 我们也需要用一种高效的方式去"阅读"代码 ...

  4. MongoDB索引操作和执行计划Explain()详解

    一.索引操作 说明,下面的内容举例时,以"dailyTrip"collection为例. 字段内容如下: { "_id" : ObjectId("63 ...

  5. AVR汇编(六):分支指令

    AVR汇编(六):分支指令 分支指令用于改变程序的执行流,分为无条件分支和条件分支两类. 无条件分支指令 JMP JMP 指令用于无条件跳转,类似于C中的 goto 关键字, JMP 指令的跳转范围为 ...

  6. SpringBoot3集成ElasticSearch

    目录 一.简介 二.环境搭建 1.下载安装包 2.服务启动 三.工程搭建 1.工程结构 2.依赖管理 3.配置文件 四.基础用法 1.实体类 2.初始化索引 3.仓储接口 4.查询语法 五.参考源码 ...

  7. QEMU tap数据接收流程

    QEMU直接从tap/tun取数据 QEMU tap数据接收步骤: qemu从tun取数据包 qemu将数据包放入virtio硬件网卡. qemu触发中断. 虚拟机收到中断,从virtio读取数据. ...

  8. 用 ChatGPT 做一个 Chrome 扩展 | 京东云技术团队

    用ChatGPT做了个Chrome Extension 最近科技圈儿最火的话题莫过于ChatGPT了. 最近又发布了GPT-4,发布会上的Demo着实吸睛. 笔记本上手画个网页原型,直接生成网页.网友 ...

  9. QA|重写了元素定位后报错xx object has no attribute 'find_element'|网页计算器自动化测试实战

    代码如下: 1 # basepage.py 2 3 from selenium import webdriver 4 5 6 class BasePage(): 7 """ ...

  10. Kali-Linux-配置开发环境

    本文主要讲解JDK.SDK.eclipse-adt.android studio.cpu模式TensorFlow 的安装配置.update:2019-08-30 03:31:46 JDK 当前系统jd ...