sun.security.tools.keytool.Main执行失败致Wildfly 10崩溃,如何仅输出错误?
我来帮你拆解下这个问题,核心原因其实和sun.security.tools.keytool.Main类的设计初衷有关——它是JDK自带的命令行版keytool工具的入口类,根本不是为服务器端代码调用设计的。当命令执行失败时(比如参数错误、证书格式不对、权限不足等),这个类会直接调用System.exit(int)方法终止整个JVM进程。而你的Wildfly服务器和这段代码运行在同一个JVM进程里,所以一旦这个方法被触发,服务器自然就跟着关闭了。
怎么避免?三种可行方案
方案1:用Java Security API手动操作证书(首推)
完全避开keytool.Main,直接用Java标准的KeyStore相关API来加载信任库、添加证书并保存。这样你能完全控制错误处理逻辑,不会触发JVM退出。示例代码如下:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; public class CertificateManager { public static void addCertificate(String trustStorePath, String trustStorePassword, String certPath) { try { // 加载目标信任库 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (FileInputStream fis = new FileInputStream(trustStorePath)) { trustStore.load(fis, trustStorePassword.toCharArray()); } // 读取要添加的X.509证书 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); try (FileInputStream certFis = new FileInputStream(certPath)) { Certificate cert = certFactory.generateCertificate(certFis); // 用时间戳作为证书别名,避免重复 String alias = "dynamic-cert-" + System.currentTimeMillis(); trustStore.setCertificateEntry(alias, cert); } // 保存更新后的信任库 try (FileOutputStream fos = new FileOutputStream(trustStorePath)) { trustStore.store(fos, trustStorePassword.toCharArray()); } System.out.println("证书添加成功"); } catch (Exception e) { // 这里只打印错误日志,不会终止服务器 System.err.println("添加证书失败:" + e.getMessage()); e.printStackTrace(); } } }
调用这个方法时,只需要传入信任库路径、密码和证书路径即可,所有异常都可以在catch块里处理,完全不会影响Wildfly运行。
方案2:把keytool作为外部进程执行
如果一定要用keytool的原生逻辑,可以通过ProcessBuilder启动外部的keytool命令,而不是在当前JVM中调用Main类。这样即使外部命令失败,也只会终止子进程,不会牵连Wildfly的JVM进程。示例代码:
import java.io.BufferedReader; import java.io.InputStreamReader; public class ExternalKeyToolRunner { public static void executeKeyToolCommand(String[] cmdArgs) { try { ProcessBuilder pb = new ProcessBuilder(cmdArgs); // 把错误输出和标准输出合并,方便读取 pb.redirectErrorStream(true); Process process = pb.start(); // 读取命令执行的输出信息 try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { // 可以把输出写入服务器日志,而不是控制台 System.out.println("[keytool输出] " + line); } } // 等待命令执行完成,获取退出码 int exitCode = process.waitFor(); if (exitCode != 0) { System.err.println("keytool命令执行失败,退出码:" + exitCode); } else { System.out.println("keytool命令执行成功"); } } catch (Exception e) { System.err.println("调用外部keytool失败:" + e.getMessage()); e.printStackTrace(); } } }
调用时传入完整的keytool命令参数,比如:
String[] cmd = { "keytool", "-importcert", "-file", "C:/certs/your-cert.crt", "-keystore", "C:/wildfly-10/standalone/configuration/truststore.jks", "-storepass", "your-truststore-password", "-noprompt" // 静默安装,不需要交互确认 }; ExternalKeyToolRunner.executeKeyToolCommand(cmd);
方案3:拦截System.exit()调用(不推荐)
可以通过自定义SecurityManager来阻止System.exit()的执行,但这种方法比较hack,而且可能会影响其他依赖System.exit()的代码(虽然服务器端场景很少见)。另外,sun.*包属于JDK内部API,未来版本可能会有变动,所以不推荐使用。示例代码仅供参考:
// 保存原始的安全管理器 SecurityManager originalSM = System.getSecurityManager(); try { // 设置自定义安全管理器,拦截exit调用 System.setSecurityManager(new SecurityManager() { @Override public void checkExit(int status) { // 抛出异常来阻止exit操作 throw new SecurityException("禁止调用System.exit()"); } }); // 调用keytool.Main的代码 sun.security.tools.keytool.Main.main(yourKeyToolArgs); } catch (SecurityException e) { System.err.println("keytool命令执行失败,已阻止JVM退出"); } finally { // 恢复原始的安全管理器 System.setSecurityManager(originalSM); }
内容的提问来源于stack exchange,提问作者Jose Pena

