Java 18及以上版本创建无需已弃用SecurityManager的RMI程序及替代方案
RMI程序中SecurityManager弃用后的替代方案
因为SecurityManager以及配套的AccessController在Java 18之后被弃用,原来用来控制RMI远程代码下载的安全机制得换思路。目前主要靠Java模块系统、自定义类加载器和RMI内置的代码源限制来实现安全管控,核心原则就是最小权限,尽量避免加载不可信的远程代码。
具体实现方案
1. 直接禁用远程代码下载(首选最安全)
如果你的RMI应用根本不需要从远程服务器加载类,直接把这个功能关掉就行,从根源杜绝风险。
- 启动JVM时加参数:
java -Djava.rmi.server.useCodebaseOnly=true
- 或者在代码里提前设置:
System.setProperty("java.rmi.server.useCodebaseOnly", "true");
这个参数会强制RMI只从本地类路径加载类,完全拒绝任何远程代码源的请求,绝大多数场景都适用。
2. 用Java模块系统锁死类访问权限
通过module-info.java定义模块化结构,严格控制模块之间的访问权限,防止远程加载的类碰敏感API:
- 只导出RMI服务必须对外暴露的包
- 限制远程类所在模块的权限,不让它访问
java.base里的敏感功能
示例module-info.java:
module com.example.rmi.server { // 只对外导出RMI服务接口的包 exports com.example.rmi.service; // 只允许RMI核心模块访问内部实现 opens com.example.rmi.impl to java.rmi; // 依赖RMI核心模块 requires java.rmi; }
3. 自定义类加载器验证代码源
如果确实需要从远程加载类,自己写个类加载器,加载前先验证代码源是否可信——比如检查是不是来自你预先指定的服务器,或者校验类文件的数字签名:
public class TrustedRMIClassLoader extends URLClassLoader { // 预先配置的可信代码源地址 private static final Set<String> TRUSTED_CODEBASES = Set.of( "https://your-trusted-server.com/rmi-classes/" ); public TrustedRMIClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 先检查本地是否已加载过该类 Class<?> loadedClass = findLoadedClass(name); if (loadedClass != null) { return loadedClass; } // 校验所有代码源是否在可信列表里 for (URL url : getURLs()) { if (!TRUSTED_CODEBASES.contains(url.toString())) { throw new SecurityException("拒绝加载不可信代码源的类:" + url); } } // 调用父类方法加载类 return super.loadClass(name, resolve); } }
然后在RMI客户端或服务器设置这个类加载器:
// 指定远程代码源地址 System.setProperty("java.rmi.server.codebase", "https://your-trusted-server.com/rmi-classes/"); // 设置上下文类加载器为自定义的可信加载器 ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(new TrustedRMIClassLoader(new URL[0], parentLoader));
4. 用细粒度权限控制替代SecurityManager
虽然SecurityManager没了,但可以用java.security.Permissions和ProtectionDomain给远程加载的类分配最小权限,避免它做超出范围的操作:
@Override protected Class<?> defineClass(String name, byte[] b, int off, int len, CodeSource cs) { // 定义仅允许必要操作的权限集合 Permissions permissions = new Permissions(); // 比如只允许读取特定系统属性 permissions.add(new PropertyPermission("java.version", "read")); // 创建保护域,绑定权限和代码源 ProtectionDomain domain = new ProtectionDomain(cs, permissions); // 用这个保护域来定义类 return defineClass(name, b, off, len, domain); }
注意事项
- 优先选禁用远程代码下载,这是最省心也最安全的方案,大部分RMI应用其实都不需要远程加载类
- 要是必须远程加载,一定要严格校验代码源的可信度,最好给类文件加数字签名,防止被篡改
- 永远遵循最小权限原则,别给远程加载的类多余的权限
内容的提问来源于stack exchange,提问作者Tel lui
相关产品推荐
相关产品推荐

