ProtectionDomain 是 Java 安全模型中连接代码来源(CodeSource)与运行权限的核心容器,类加载时自动绑定,权限检查时沿调用栈逐个验证各 ProtectionDomain 的授权。

ProtectionDomain 是 Java 安全模型中连接“代码来源”和“运行权限”的核心桥梁。它本身不直接定义权限,而是作为容器,把一段代码(由 CodeSource 标识)和它被授予的 Permission 集合绑定起来。看懂它,关键不是背概念,而是理清它在类加载与访问控制中的实际流转位置。
CodeSource 是 ProtectionDomain 的“身份证”
每个 ProtectionDomain 都持有一个 CodeSource 实例,而 CodeSource 只做两件事:
- 记录代码从哪儿来——比如
file:/app/lib/plugin.jar或https://example.com/applet.class - 记录代码由谁签名——即一组公钥(对应签名者的私钥),用于验证字节码未被篡改
注意:同一个类名、不同来源(比如本地 jar 和远程下载的 class),会得到不同的 CodeSource,也就属于不同的 ProtectionDomain —— 这是实现“隔离”的基础,不是靠类名或包名区分,而是靠“谁给的 + 谁签的”。
类加载时自动关联 ProtectionDomain
当类加载器(如 URLClassLoader)成功加载一个类后,JVM 会为该类自动创建一个 ProtectionDomain,并绑定其 CodeSource 和当前策略(Policy)所授予的权限。这个过程对开发者透明,但有几点必须知道:
- Bootstrap 类加载器加载的核心类(如
java.lang.String)使用的是“无限制”的系统级 ProtectionDomain,权限由 JVM 内置保障 - 自定义类加载器若未显式指定 ProtectionDomain,JVM 会按默认策略生成;若调用
defineClass(String, byte[], int, int, CodeSource),则可主动传入 CodeSource,从而影响后续权限判定 - 同一个类加载器多次加载同一份字节码,只要 CodeSource 相同,通常复用同一个 ProtectionDomain
权限检查时,AccessController 看的是栈上所有 ProtectionDomain
当代码执行到 FileInputStream 构造函数这类敏感操作时,SecurityManager 会调用 checkRead(),最终委托给 AccessController.checkPermission()。这时它不是查单个类的权限,而是沿当前线程执行栈向上扫描,收集所有已执行方法所属类的 ProtectionDomain,然后逐个检查它们是否都“授权”了该 Permission。
例如:A 类(来自本地 trusted.jar)调用了 B 类(来自网络 untrusted.jar),B 类又试图读文件。即使 A 有读权限,只要 B 所属的 ProtectionDomain 没被授予 FilePermission,“检查”就会失败 —— 这就是所谓的“特权退让”(privilege dropping)机制。
调试与观察的实际方法
想确认某个类的 ProtectionDomain 是什么,可在运行时打印:
Class<?> clazz = YourClass.class;
ProtectionDomain pd = clazz.getProtectionDomain();
CodeSource cs = pd.getCodeSource();
System.out.println("Location: " + (cs == null ? "null" : cs.getLocation()));
System.out.println("Signers: " + java.util.Arrays.toString(cs.getSigners()));
System.out.println("Permissions: " + pd.getPermissions());
配合启用安全策略文件(-Djava.security.manager -Djava.security.policy=my.policy),就能看到不同来源代码拿到的权限差异。不复杂但容易忽略。

















