在 JDK 11.0.11 上使用 '-Djava.system.class.loader' 时出现异常,如果 class 加载程序在已签名的 JAR 中

Exception when using '-Djava.system.class.loader' on JDK 11.0.11, if class loader is in signed JAR

我在 start/run Java Java 工具套件 4.11(基于 Eclipse 2021-06)中使用 Spring 工具套件中的 AspectJ Load-Time-Weaving 应用程序时遇到问题=] 11.0.11 运行时(使用 AdoptOpenJDK 11.0.11.9-hotspot 或 Oracle JDK build 11.0.11+9-LTS-194 或 Zulu11.48+21-CA(build 11.0.11 +9-LTS) 在 Windows 上 10) 当访问 javax.net.ssl.SSLContext 时。对于较旧的 Java 11 版本,不会出现此问题。 我已将问题简化为一个小应用程序:

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException {
        String cp = System.getProperty("java.class.path");
        System.err.println(cp);
        String cn = System.getProperty("java.system.class.loader");
        System.err.println(cn);
        final SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, null, null);
    }
}   

该应用程序在 Eclipse 中使用 AspectJ LTW 启动配置启动。 我得到的输出和异常是这样的:

D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar
org.aspectj.weaver.loadtime.WeavingURLClassLoader

Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class sun.security.jca.ProviderConfig$ProviderLoader
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:266)
    at java.base/sun.security.jca.ProviderList$ServiceList.tryGet(ProviderList.java:511)
    at java.base/sun.security.jca.ProviderList$ServiceList.hasNext(ProviderList.java:565)
    at java.base/java.security.Signature.getInstance(Signature.java:266)
    at java.base/sun.security.ssl.JsseJce.getSignature(JsseJce.java:202)
    at java.base/sun.security.ssl.JsseJce$EcAvailability.<clinit>(JsseJce.java:394)
    at java.base/sun.security.ssl.JsseJce.isEcAvailable(JsseJce.java:175)
    at java.base/sun.security.ssl.CipherSuite$KeyExchange.isAvailable(CipherSuite.java:1079)
    at java.base/sun.security.ssl.CipherSuite.isAvailable(CipherSuite.java:941)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableCipherSuites(SSLContextImpl.java:384)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableSupportedCipherSuites(SSLContextImpl.java:347)
    at java.base/sun.security.ssl.SSLContextImpl$AbstractTLSContext.<clinit>(SSLContextImpl.java:580)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at java.base/java.security.Provider$Service.getImplClass(Provider.java:1918)
    at java.base/java.security.Provider$Service.newInstance(Provider.java:1894)
    at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
    at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
    at java.base/javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
    at org.pmd.bug.Main.main(Main.java:18)

这是我使用 VM 参数 "-Djava.security.debug="jca":

启用安全调试日志时的完整输出
ProviderList: provider configuration: [SUN, SunRsaSign, SunEC, SunJSSE, SunJCE, SunJGSS, SunSASL, XMLDSig, SunPCSC, JdkLDAP, JdkSASL, SunMSCAPI, SunPKCS11]
ProviderList: config configuration: null
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Loading all providers
java.lang.Exception: Debug Info. Call trace:
    at java.base/sun.security.jca.ProviderList.loadAll(ProviderList.java:311)
    at java.base/sun.security.jca.ProviderList.removeInvalid(ProviderList.java:332)
    at java.base/sun.security.jca.Providers.getFullProviderList(Providers.java:165)
    at java.base/java.security.Security.getProviders(Security.java:457)
    at java.base/sun.security.x509.AlgorithmId.computeOidTable(AlgorithmId.java:637)
    at java.base/sun.security.x509.AlgorithmId.oidTable(AlgorithmId.java:627)
    at java.base/sun.security.x509.AlgorithmId.algOID(AlgorithmId.java:609)
    at java.base/sun.security.x509.AlgorithmId.get(AlgorithmId.java:441)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:380)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.pkcs.SignerInfo.getTimestamp(SignerInfo.java:545)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:318)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:283)
    at java.base/sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:259)
    at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:316)
    at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:230)
    at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:759)
    at java.base/java.util.jar.JarFile.ensureInitialization(JarFile.java:1038)
    at java.base/java.util.jar.JavaUtilJarAccessImpl.ensureInitialization(JavaUtilJarAccessImpl.java:69)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader.getManifest(URLClassPath.java:870)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:786)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at java.base/java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1976)
    at java.base/java.lang.System.initPhase3(System.java:2074)
ProviderConfig: Loading provider SunEC
ProviderConfig: Error loading provider SunEC
java.lang.ExceptionInInitializerError
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.loadAll(ProviderList.java:315)
    at java.base/sun.security.jca.ProviderList.removeInvalid(ProviderList.java:332)
    at java.base/sun.security.jca.Providers.getFullProviderList(Providers.java:165)
    at java.base/java.security.Security.getProviders(Security.java:457)
    at java.base/sun.security.x509.AlgorithmId.computeOidTable(AlgorithmId.java:637)
    at java.base/sun.security.x509.AlgorithmId.oidTable(AlgorithmId.java:627)
    at java.base/sun.security.x509.AlgorithmId.algOID(AlgorithmId.java:609)
    at java.base/sun.security.x509.AlgorithmId.get(AlgorithmId.java:441)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:380)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.pkcs.SignerInfo.getTimestamp(SignerInfo.java:545)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:318)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:283)
    at java.base/sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:259)
    at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:316)
    at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:230)
    at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:759)
    at java.base/java.util.jar.JarFile.ensureInitialization(JarFile.java:1038)
    at java.base/java.util.jar.JavaUtilJarAccessImpl.ensureInitialization(JavaUtilJarAccessImpl.java:69)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader.getManifest(URLClassPath.java:870)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:786)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at java.base/java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1976)
    at java.base/java.lang.System.initPhase3(System.java:2074)
Caused by: java.lang.IllegalStateException: getSystemClassLoader cannot be called during the system class loader instantiation
    at java.base/java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1932)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<init>(ProviderConfig.java:323)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<clinit>(ProviderConfig.java:313)
    ... 38 more
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar
org.aspectj.weaver.loadtime.WeavingURLClassLoader
Hello World!
ProviderConfig: Loading provider SunJGSS
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class sun.security.jca.ProviderConfig$ProviderLoader
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:266)
    at java.base/sun.security.jca.ProviderList$ServiceList.tryGet(ProviderList.java:511)
    at java.base/sun.security.jca.ProviderList$ServiceList.hasNext(ProviderList.java:565)
    at java.base/java.security.Signature.getInstance(Signature.java:266)
    at java.base/sun.security.ssl.JsseJce.getSignature(JsseJce.java:202)
    at java.base/sun.security.ssl.JsseJce$EcAvailability.<clinit>(JsseJce.java:394)
    at java.base/sun.security.ssl.JsseJce.isEcAvailable(JsseJce.java:175)
    at java.base/sun.security.ssl.CipherSuite$KeyExchange.isAvailable(CipherSuite.java:1079)
    at java.base/sun.security.ssl.CipherSuite.isAvailable(CipherSuite.java:941)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableCipherSuites(SSLContextImpl.java:384)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableSupportedCipherSuites(SSLContextImpl.java:347)
    at java.base/sun.security.ssl.SSLContextImpl$AbstractTLSContext.<clinit>(SSLContextImpl.java:580)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at java.base/java.security.Provider$Service.getImplClass(Provider.java:1918)
    at java.base/java.security.Provider$Service.newInstance(Provider.java:1894)
    at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
    at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
    at java.base/javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
    at org.pmd.bug.Main.main(Main.java:18)

在我看来,当系统 class 加载程序在 Classloader#initSystemClassLoader 中初始化为 org.aspectj.weaver.loadtime.WeavingURLClassLoader 时,一些步骤之后会以某种方式触发所有安全提供程序的加载,而这又需要访问系统 class 加载程序(尚未完全初始化)。 这导致日志的这一部分:

Caused by: java.lang.IllegalStateException: getSystemClassLoader cannot be called during the system class loader instantiation
    at java.base/java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1932)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<init>(ProviderConfig.java:323)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<clinit>(ProviderConfig.java:313)

稍后在应用程序中调用 SSLContext.getInstance("TLS"),再次需要 sun.security.jca.ProviderConfig$ProviderLoader,但这次因 NoClassDefFoundError 而失败。

你觉得我的分析正确吗?

这是 OpenJDK 11.0.11 中的(潜在)错误吗?

这个问题有解决办法吗?

更新 (06.07.21):

I found out 如何查看 Eclipse 启动配置产生的完整命令行调用。这是:

C:\Programme\Java\jdk-11.0.11.9-hotspot\bin\javaw.exe -Djava.security.debug=jca -Djava.system.class.loader=org.aspectj.weaver.loadtime.WeavingURLClassLoader -Daj.class.path=D:\workspace\sts490\edrewemaster\example-demo\target\classes;D:\workspace\sts490\edrewemaster\example-demo\target\test-classes;D:\repo\org\aspectj\aspectjrt.9.6\aspectjrt-1.9.6.jar;D:\workspace\sts490\edrewemaster\example-demo\target\classes -Dfile.encoding=Cp1252 -classpath D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar org.pmd.bug.Main

当我像这样从命令行 运行 时(使用 java 而不是 javaw),我也得到了异常。当我使用像

这样的旧 Java 版本 (11.0.10) 时
C:\Programme\Java.0.10_9_adopt\jdk-11.0.10+9\bin\java ...

有效。

所以 Eclipse 似乎取代了 LTW 的系统 class 加载程序。有没有办法告诉 Eclipse 将 LTW 与 java 代理解决方案一起使用?也许这可以解决这个问题?

我想我找到了解决方法:不要使用类型为“AspectJ Load-Time Weaving Application”的 Eclipse 启动配置,而是使用普通的 Java 应用程序启动配置并通过“-javaagent”标志启用 LTW,例如

-javaagent:D:\repo\org\aspectj\aspectjweaver.9.6\aspectjweaver-1.9.6.jar

这对我有用。

我做了几个实验,发现问题所在

  • 与AspectJ完全无关,
  • 一般来说与Java代理完全无关,
  • 发生当且仅当
      使用
    • -Djava.system.class.loader
    • 有问题的系统 class 加载程序在签名的 JAR 中找到并且
    • 该应用程序在 JDK 11.0.11 上运行(不是 11.0.10 或我测试过的任何其他 JDK 9-16)。

一旦我从 META-INF 目录中删除 JAR 签名,它也适用于 JDK 11.0.11。 IMO,这是 JDK 中的回归错误,应该修复。这是一个最小的测试用例:

package org.acme.app;

public class Main {
  public static void main(String[] args) {}
}
package org.acme.loader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public class CustomClassLoader extends ClassLoader {

  public CustomClassLoader(ClassLoader parent) {
    super(parent);
  }

  @Override
  public Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] b = loadClassFromFile(name);
    return defineClass(name, b, 0, b.length);
  }

  private byte[] loadClassFromFile(String fileName) {
    InputStream inputStream = getClass().getClassLoader()
      .getResourceAsStream(fileName.replace('.', File.separatorChar) + ".class");
    byte[] buffer;
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    int nextValue = 0;
    try {
      while ((nextValue = inputStream.read()) != -1) {
        byteStream.write(nextValue);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    buffer = byteStream.toByteArray();
    return buffer;
  }
}

请确保两个 classes 在不同的包中,否则稍后 JVM 会抱怨它们没有被签名。

我们在基础目录中,源代码在文件夹src.

# Compile source files (JDK used for compilation is unimportant)
javac --release 8 src\org\acme\app\Main.java src\org\acme\loader\CustomClassLoader.java

# Create JAR containing custom class loader
jar cf CustomClassLoader.jar -C src org\acme\loader\CustomClassLoader.class

# Create signing key (default keystore has password 'changeit')
keytool -genkeypair -keyalg RSA -alias test-user

# Sign JAR (default keystore has password 'changeit')
jarsigner CustomClassLoader.jar test-user

# Run dummy application, setting custom class loader from JAR as system class loader
"c:\Program Files\Java\jdk-11.0.11\bin\java.exe" -Djava.security.debug="jca" -Djava.system.class.loader=org.acme.loader.CustomClassLoader -cp "CustomClassLoader.jar;src" org.acme.app.Main

改变 java.exe 的路径,以查看问题确实只发生在 JDK 11.0.11.


更新: 我刚刚向 Oracle 提交了一份 JDK 错误报告(内部审核 ID 9070863)。一旦收到他们的来信并且该错误在 public 错误数据库中,我将再次更新此答案。

更新 2: 错误报告 JDK-8270170 经过验证后现在可见。