SecurityManager弃用后,如何通过Java反射实现反序列化器白名单?
用Java模块系统替代SecurityManager控制suppressAccessChecks权限
在Java 9+中,废弃SecurityManager后,模块系统确实是替代它实现suppressAccessChecks权限白名单控制的最佳方案,以下是具体实现步骤和思路:
1. 模块声明层面的权限控制(核心)
通过module-info.java的静态声明,可以精准指定哪些模块能获得suppressAccessChecks权限,无需动态代码检查。
假设你的反序列化器模块是com.example.deserializer,被反射操作的目标模块是com.example.target,在目标模块的module-info.java中添加:
module com.example.target { // 仅允许白名单模块访问目标内部类(可选,配合反射使用) opens com.example.target.internal to com.example.deserializer; // 直接授予白名单模块suppressAccessChecks权限 grants java.lang.reflect.ReflectPermission "suppressAccessChecks" to com.example.deserializer; }
grants语句是模块系统专门用于权限授予的语法,只有指定的模块能获取该反射权限,其他模块调用时会直接抛出SecurityException。
2. 代码层面的动态白名单校验(可选增强)
如果需要像之前那样做动态调用者校验,用Java 9引入的StackWalker替代StackTraceElement,能更高效地获取调用者的模块信息:
public void validateSuppressAccessCaller() { // 获取调用者的类与模块信息 StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); Module callerModule = walker.getCallerClass().getModule(); // 定义白名单模块 Set<String> allowedModules = Set.of("com.example.deserializer"); if (!allowedModules.contains(callerModule.getName())) { throw new SecurityException("模块[" + callerModule.getName() + "]无权限调用suppressAccessChecks"); } }
在需要调用suppressAccessChecks的代码前,先执行这个校验方法即可。
3. 运行时参数的动态调整(教学演示用)
在教学演示时,可以用JVM参数快速切换权限规则,展示不同场景的效果:
- 临时允许某个模块访问:
--add-opens com.example.target/internal=com.example.deserializer - 临时授予权限:
--add-grants java.base/java.lang.reflect=com.example.deserializer - 拒绝所有非白名单模块:无需额外参数,模块系统会默认拦截未授权的调用
模块系统方案的优势
- 更可靠:静态声明的权限由JVM强制校验,很难被绕过,比SecurityManager的动态代码检查更安全。
- 粒度更细:可以针对特定包、类甚至单个权限做精确控制,而非全局的权限拦截。
- 性能更高:模块系统的权限检查是JVM层面优化的,比遍历堆栈的开销小很多。
内容的提问来源于stack exchange,提问作者Marco Servetto
相关产品推荐
相关产品推荐

