前言:

在看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. .net core 因路径原因导致的JSON解析错误

    因解析json配置文件导致的错误: JsonReaderException: '0xEF' is an invalid escapable character within a JSON string ...

  2. 好用工具: Carbon--代码美化工具

    问题背景 当进行PPT展示时,如果只是简单的复制粘贴代码.会看起来很难看,因此产生美化的想法. 使用方式 官网链接 https://carbon.now.sh

  3. pandas 格式化日期

    output_data["ShipDate"] = output_data["ShipDate"].dt.strftime("%Y/%m/%d&quo ...

  4. 王道oj/problem15(用c++的引用精简代码)

    网址:http://oj.lgwenda.com/problem/15 思路:子函数的形参是指针的时候格式为 int*&p,且原函数实参为p 主函数使用fgets(字符串的指针,最大容量,st ...

  5. Jenkins远程执行shell脚本,超时报错(ERROR: Exception when publishing, exception message [Exec timed out or was interrupted after 120,001 ms])

    通过Jenkins远程执行脚本时,发现Jenkins构建任务失败,但是服务器本身的脚本是生效的. 排查确认是Jenkins构建任务超时,导致Jenkins看板显示任务构建失败,但是服务器上的自动脚本执 ...

  6. 不重启Docker能添加自签SSL证书镜像仓库吗?

    应用背景 在企业应用Docker规划初期配置非安全镜像仓库时,有时会遗漏一些仓库没配置,但此时应用程序已经在Docker平台上部署起来了,体量越大就越不会让人去直接重启Docker. 那么,不重启Do ...

  7. 【Hexo】插件推荐以及使用小技巧

    目录 插件推荐 hexo-deployer-git hexo-word-counter hexo-abbrlink hexo-generator-sitemap 小技巧 自定义提交信息 参考资料 He ...

  8. 【SQL】所谓的连表查询

    连表查询 外连接 外连接分为两种,左(外)连接和右(外)连接 基本语法如下: SELECT 字段列表 FROM 表1 LEFT JOIN 表2 ON 条件; 这是左连接,因此以表1中的 [字段列表] ...

  9. 《Kali渗透基础》13. 无线渗透(三)

    @ 目录 1:无线通信过程 1.1:Open 认证 1.2:PSK 认证 1.3:关联请求 2:加密 2.1:Open 无加密网络 2.2:WEP 加密系统 2.3:WPA 安全系统 2.3.1:WP ...

  10. 如何通过API接口获取淘宝的店铺所有商品详情

    在电子商务领域中,淘宝是亚洲最大的在线交易平台之一,拥有海量的商品资源和消费者.如果你是一名开发者,想要在自己的网站或者APP中嵌入淘宝商品资源,那么你就需要通过淘宝开放平台提供的API接口来获取这些 ...