Java 17与Spring Boot 3.x可执行Jar的Security Manager问题求助
问题场景
项目从Java 8/Spring Boot 2.x迁移至Java 17/Spring Boot 3.x,因内部需求必须保留自定义Security Manager。使用java -classpath <所有类和依赖> <主类>启动正常,但通过java -jar <可执行Jar>启动时出现大量Logger、Ignite等类找不到的异常,关闭Security Manager则无报错。自定义Security Manager启用代码如下:
log.warn("Enabling PlatformSecurityManager"); Policy policy = Policy.getPolicy(); PermissionCollection perm = policy.getPermissions(new CodeSource(null, (Certificate[]) null)); boolean canReplaceSecurityManager = perm.implies(new RuntimePermission("setSecurityManager")); if (System.getSecurityManager() != null && !canReplaceSecurityManager) { //we have to do the check like this because it will be from a different classloader if (!(System.getSecurityManager().getClass().getName().equals(PlatformSecurityManager.class.getName()))) { throw new RuntimeException( "You have started the JVM with a security manager that doesn't allow me to set a new one!"); } //else another platform application has specified the security manager... so we should be ok } else { if (System.getProperty("java.security.policy") == null) { log.warn("java.security.policy Not found"); //load up allow all policy log.info("Enabling AllPermission for the platform security manager"); PlatformSecurityPolicy platformPolicy = new PlatformSecurityPolicy(); platformPolicy.addPermission(new AllPermission()); Policy.setPolicy(platformPolicy); } log.warn("Enabled PlatformSecurityManager"); System.setSecurityManager(new PlatformSecurityManager(false)); }
核心原因
Spring Boot可执行Jar使用LaunchedURLClassLoader加载BOOT-INF/lib下的嵌套依赖,与普通classpath启动的类加载逻辑存在差异。结合Java 17中Security Manager的废弃特性,当前自定义Policy的权限覆盖范围未适配该类加载器场景,导致读取嵌套Jar资源时权限不足,触发类找不到异常。
解决思路
1. 补全自定义Policy的类加载相关权限
当前代码添加的AllPermission可能因类加载器上下文问题未生效到LaunchedURLClassLoader,需显式补充类加载和Jar资源读取权限:
if (System.getProperty("java.security.policy") == null) { log.warn("java.security.policy Not found"); log.info("Enabling permissions for platform security manager"); PlatformSecurityPolicy platformPolicy = new PlatformSecurityPolicy(); // 保留全权限兜底 platformPolicy.addPermission(new AllPermission()); // 补充Jar资源读取权限 platformPolicy.addPermission(new URLPermission("jar:*", "read")); // 补充本地Jar文件读取权限 platformPolicy.addPermission(new FilePermission("<<ALL FILES>>", "read")); // 补充类加载相关权限 platformPolicy.addPermission(new RuntimePermission("getClassLoader")); platformPolicy.addPermission(new RuntimePermission("setContextClassLoader")); Policy.setPolicy(platformPolicy); }
2. 调整Security Manager启动时机
Spring Boot 3.x启动流程中,过早设置Security Manager可能干扰LaunchedURLClassLoader初始化,可尝试在应用启动完成后再初始化:
public static void main(String[] args) { // 先启动Spring Boot应用 ConfigurableApplicationContext context = SpringApplication.run(MainClass.class, args); // 再初始化Security Manager initSecurityManager(); } private static void initSecurityManager() { // 放入原有的Security Manager初始化代码 }
3. 排查具体权限缺失
重写自定义Security Manager的checkPermission方法,打印被拒绝的权限和调用栈,精准定位缺失项:
public class PlatformSecurityManager extends SecurityManager { // 原有构造方法和逻辑 @Override public void checkPermission(Permission perm) { try { super.checkPermission(perm); } catch (SecurityException e) { System.err.println("Permission denied: " + perm); e.printStackTrace(); throw e; } } }
4. 改用JVM启动参数指定Security Manager
避免代码动态设置的上下文问题,直接通过JVM参数强制指定:
java -Djava.security.manager=com.your.package.PlatformSecurityManager \ -Djava.security.policy==/path/to/your/policy-file.policy \ -jar your-executable.jar
注意java.security.policy参数使用两个等号,表示强制替换系统默认Policy而非追加。
5. 适配Java 17的CodeSource逻辑
Java 17对null CodeSource的权限处理行为变化,尝试使用当前类的CodeSource替代:
// 替换原有的null CodeSource CodeSource currentCodeSource = MainClass.class.getProtectionDomain().getCodeSource(); PermissionCollection perm = policy.getPermissions(currentCodeSource);
内容的提问来源于stack exchange,提问作者Nishant Modi

