java安全管理器(securitymanager)因与现代模块化、容器化及动态加载冲突严重,自jdk 17废弃、jdk 21彻底移除,运行时调用会抛unsupportedoperationexception;替代方案需分层实施:os级隔离、模块系统控制可见性、框架级权限注解及字节码增强等。

Java安全管理器(SecurityManager)为什么默认禁用了
因为从 JDK 17 开始,SecurityManager 被标记为废弃(deprecated),JDK 21 彻底移除——不是“不推荐用”,是运行时直接不认这个类了。你写 System.setSecurityManager(new SecurityManager()),会抛 UnsupportedOperationException。
根本原因不是沙箱过时,而是它太重、太静态、与现代模块化(JPMS)、容器化、动态类加载(如 Spring Boot DevTools、GraalVM Native Image)冲突严重。实际项目里,没人靠它拦住恶意字节码;真要隔离,现在靠 OS 级别(cgroup、namespace)、容器编排或专用沙箱(如 WebAssembly 运行时)更靠谱。
实操建议:
- 不要在新项目中尝试启用
SecurityManager,哪怕只是测试 - 如果维护老系统(JDK 8–11),确认启动参数含
-Djava.security.manager,且java.security策略文件路径正确(默认是${java.home}/lib/security/java.policy) - 策略文件里用
grant { ... }块声明权限时,注意java.io.FilePermission的路径通配符写法:">"不等价于"*",后者只匹配当前目录下的文件
代码签名(JAR Signing)还能防止什么
能防篡改,不能防恶意逻辑。签名验证的是「这个 JAR 自从被开发者签完就没被改过」,不是「这个 JAR 是安全的」。用户双击运行一个带有效签名的恶意 JAR,只要没开安全策略,照样能删文件、连外网。
立即学习“Java免费学习笔记(深入)”;
关键点在于:签名本身不触发任何访问控制;它只是给 SecurityManager 或自定义类加载器提供一个可信锚点。而如今 SecurityManager 没了,签名就只剩校验完整性这一个作用。
实操建议:
- 用
jarsigner签名时,务必加-tsa参数指定时间戳服务(如http://timestamp.digicert.com),否则证书过期后 JAR 就无法验证通过 - 不要依赖
Manifest.MF里的Permissions: sandbox—— 这个字段只对 Java Web Start 有效,而 Web Start 早在 JDK 11 就被删除了 - 如果你在用
ClassLoader.defineClass()动态加载字节码,签名信息不会自动继承;得手动调用CodeSource构造并传入已验证的Certificate[]
替代 SecurityManager 的权限控制怎么做
没有统一替代品。真实场景下,权限控制被拆解到不同层级:OS 层控资源、应用层控 API、框架层控行为。
比如你想限制某个插件读配置文件,与其指望 JVM 拦截 FileInputStream,不如让插件只通过你提供的 ConfigService.read(String key) 接口访问,并在该方法里做白名单校验。
实操建议:
- 用 Java 9+ 的模块系统(JPMS)控制包可见性:
module-info.java中用exports和opens显式声明,比运行时反射拦截更早、更确定 - Spring Security 或 Shiro 这类框架的权限注解(如
@PreAuthorize)只管业务方法入口,不拦底层 IO;它们和 JVM 沙箱完全不在一个抽象层 - 如果必须动态限制字节码行为,考虑使用
Instrumentation+ 字节码增强(如 Byte Buddy),在ClassFileTransformer中检查是否调用敏感 API(如Runtime.exec),但要注意性能损耗和 ClassLoader 隔离问题
策略文件(.policy)现在还有谁在用
极少数遗留系统:银行核心批处理作业、老版 OSGi 容器、某些国产中间件的插件机制。普通 Maven/Gradle 项目基本见不到。而且这些系统往往自己实现了策略解析逻辑,不依赖 JDK 内置的 Policy 子类。
真正容易踩的坑是:以为改了 java.security 文件就能全局生效。实际上,每个 ClassLoader 可以绑定独立的 Policy 实例,而默认策略只对系统类加载器生效;你的应用类加载器很可能压根没读那个文件。
实操建议:
- 不要在生产环境依赖
java.policy做关键权限控制——它缺乏审计日志、无法热更新、策略语法难调试 - 如果必须解析策略文件,用
Policy.getInstance("JavaPolicy", ...)而非硬编码new PolicyFile(...),否则在 GraalVM Native Image 下会失败 -
permission java.net.SocketPermission "example.com:80", "connect";这种写法在 IPv6 环境下可能失效,建议显式写成"example.com:80-80"或加上"resolve"










