Java模块化安全核心是强封装:未导出包内public类对外不可见,连反射都无法访问,必须通过导出接口受控调用,结合依赖显式声明与运行时加固实现纵深防御。

核心思路是:不让变量“露出来”,而只提供受控的使用方式。模块化不是给代码加个文件夹,而是用语言机制建一堵墙——墙内变量,墙外连反射都够不着。
把工具类和变量锁进未导出包
在 Java 9+ 的 JPMS 中,敏感或内部工具类(比如 StringUtils、ConfigHelper)必须放在明确不导出的包里,例如 internal.util 或 core.support:
- module-info.java 中不写 exports internal.util,哪怕这个包里有 public 类
- 即使其他模块声明了 requires 你的模块,也无法 import internal.util.*,编译直接报错
- 运行时连 Class.forName("internal.util.ConfigHelper") 都会抛 ModuleFindException
只暴露接口,不暴露实现和常量
禁止用 public static final String API_TIMEOUT = "3000" 这类明文常量。这类变量一旦出现在 classpath,就等于全局公开:
- 定义一个接口如 TimeoutProvider,导出到 public 包(如 api.config)
- 把实际值藏在 internal.config.DefaultTimeoutProvider 里,该类所在包不导出
- 外部只能调用 timeoutProvider.getApiTimeout(),无法读取原始字段
用模块依赖约束替代“大家都能用”的惯性
很多错误依赖源于历史习惯:一个 common-utils.jar 被十几个项目直接引用,没人知道谁改了它、谁依赖了它的某个内部字段:
- 新模块只声明 requires java.base 和真正需要的 API 模块,不加任何 “utils” “helper” 类自动模块
- 构建时用 jdeps --module-path lib/ --check your-module 扫描非法跨包引用
- CI 流水线中加入检查:若发现 import internal.* 或反射访问 internal 包,立即失败
配合运行时加固,堵住最后漏洞
模块封装是第一道防线,但还需防止绕过:
- 启动参数加上 --add-opens java.base/java.lang=ALL-UNNAMED 这类开放仅限必要项,禁用全开
- 禁用 SecurityManager 不代表放弃防护,可用 JVM TI agent 或字节码插桩拦截对 internal 包的非法反射调用
- 在日志或监控中埋点:当检测到非授权模块尝试加载 internal 类时,记录告警而非静默失败


















