Java tomcat类加载器bug?

Java tomcat类加载器bug?,java,tomcat,bouncycastle,Java,Tomcat,Bouncycastle,更新:我已经了解了更多正在发生的事情,并在底部添加了新的信息 我有两个应用程序在tomcat下运行。首先加载App1,然后加载App2。如果App1在启动过程中遇到任何类型的错误并未能成功加载,则我在App2启动过程中会遇到以下错误: Caused by: java.security.NoSuchAlgorithmException: No such algorithm: RSA/NONE/OAEPWithSHA1AndMGF1Padding at javax.crypto.Cipher

更新:我已经了解了更多正在发生的事情,并在底部添加了新的信息

我有两个应用程序在tomcat下运行。首先加载App1,然后加载App2。如果App1在启动过程中遇到任何类型的错误并未能成功加载,则我在App2启动过程中会遇到以下错误:

Caused by: java.security.NoSuchAlgorithmException: No such algorithm: RSA/NONE/OAEPWithSHA1AndMGF1Padding
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at javax.crypto.Cipher.getInstance(DashoA13*..)
    at com.jp.protection.security.BouncyCastleSecurityProvider.getCipher(BouncyCastleSecurityProvider.java:139)
    at com.jp.protection.security.BouncyCastleSecurityProvider.decode(BouncyCastleSecurityProvider.java:110)
    ... 70 more
Caused by: java.lang.NullPointerException
    at org.bouncycastle.jcajce.provider.util.DigestFactory.getDigest(DigestFactory.java:86)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.initFromSpec(CipherSpi.java:83)
    at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineSetPadding(CipherSpi.java:214)
    at javax.crypto.Cipher$r.a(DashoA13*..)
    ... 74 more
请注意,最终原因是NullPointerException。我下载了DigestFactory的源代码,如下所示(仅摘录相关部分):

在第86行获取NPE的唯一方法是如果sha1为空。(如果digestName为null,NPE将出现在对Strings.toUpperCase的调用中。)事实上,如果我在这里放置断点,在错误场景下,调试器将sha1(以及所有其他类似静态初始化的字段)显示为null。这些字段是私有的,没有允许修改这些字段的方法

这怎么可能?我想也许我的DigestFactory源代码与我正在运行的jar不完全匹配,因此调试器误导了我,但它应该是正确的版本,而且其他所有内容似乎都是一致的

在调试器下,我尝试在App2初始化的早期阶段调用DigestFactory.getDigest(“SHA-1”)(使用调试器的求值表达式),在异常发生之前,它成功返回。这表明DigestFactory的静态字段已成功初始化,然后以某种方式稍后设置为null,或者另一个类加载器具有不同版本的类(即使是这种情况,也无法解释它们如何为null)

这个异常发生在第三方代码的两层深处(JPProductivity Protection package使用bouncycastle),因此我对这种情况的控制是有限的。然而,我想首先了解这是如何可能的,并希望我能如何预防或解决它。另一个谜团是为什么第一个应用程序的错误会对第二个应用程序产生影响——在tomcat下,这些应用程序应该有单独的类加载器。但是如果第一个应用程序中没有错误,那么第二个应用程序中就不会出现此问题

更新:自从我发布这篇文章以来,我学到了更多。当一个webapp被tomcat停止时(在这种情况下是因为启动错误),tomcat会将其类中的静态字段设置为null,以避免内存泄漏。这就解释了我的不可变静态字段是如何设置为null的。但是,这不应该影响App2,因为它应该使用单独的类加载器。但我已经查看了调试器,事实上,两个webapp中的DigestFactory类都使用了相同的类加载器。这与我能找到的所有tomcat文档相矛盾。对于其他类(我自己的类),有不同的类加载器。我想知道它是否与DigestFactory是静态的和不可变的有关,所以理论上它来自哪里并不重要


因此,作为一个实验,我从两个webapp中删除了包含DigestFactory的jar,并将其添加到tomcat/lib中(这样它就可以共享,而不是任何一个webapp的一部分)。这解决了这个问题——它的字段并没有被置为空,大概是因为它不是有问题的webapp的一部分。然而,这种方法是不可取的,不应该是必要的。这是一只雄猫虫吗

我认为正在发生的是,当您启动App1时,bouncycastle会在应用程序类加载器下注册。如果我没有记错的话,那么提供程序将通过一些静态初始值设定项或方法注册到JVM类加载器中

当您的App1崩溃(或重新部署)时,它的类加载器以及它加载的所有类(包括bouncycastle)都将被删除。结果是JVM认为它仍然存在,因为它仍然注册,而实际上它没有

解决方案是在jre/lib/security/java.security(java 7位置,我认为它在较旧版本的jre/lib/ext中)的安全提供程序列表中添加BouncyCastleProvider,方法如下:

security.provider。[下一个可用号码]=org.bouncycastle.jce.provider.BouncyCastleProvider


您可能也需要在那里添加jar文件。

在断定这一定是一个tomcat错误之后,我从tomcat 6升级到了tomcat 7,并解决了这个问题。

我发现BouncyCastle提供程序在我的WebApp WEB-INF/lib中包含时有类似的行为,并且我重新加载了我的tomcat应用程序。在我的案件中提出了以下免责:

júl. 09, 2015 7:24:00 DE org.apache.catalina.loader.WebappClassLoader loadClass
INFO: Illegal access: this web application instance has been stopped already.  Could not load org.bouncycastle.jcajce.provider.digest.SHA1$PBEWithMacKeyFactory.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1612)
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
        at org.apache.tomee.catalina.LazyStopWebappClassLoader.loadClass(LazyStopWebappClassLoader.java:129)
        at java.security.Provider$Service.getImplClass(Provider.java:1279)
        at java.security.Provider$Service.newInstance(Provider.java:1237)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
        at javax.crypto.JceSecurity.getInstance(JceSecurity.java:116)
        at javax.crypto.SecretKeyFactory.getInstance(SecretKeyFactory.java:243)
        at org.bouncycastle.jcajce.util.ProviderJcaJceHelper.createSecretKeyFactory(Unknown Source)
        at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.calculatePbeMac(Unknown Source)
        at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(Unknown Source)
        at java.security.KeyStore.load(KeyStore.java:1214)
        at hu.myapp.mypackage.myEJB.setup(myEJB.java:154)
其中,setup是myEJB stateless中的@PostConstruct注释方法,用于初始化BC提供程序

@LocalBean
@Stateless
public class myEJB {
  ...
  private BouncyCastleProvider bc = null;
  private KeyStore ks = null;

  @PostConstruct
  protected void setup() {
        bc = new BouncyCastleProvider();
        ks = KeyStore.getInstance("PKCS12", bc);
        ...
  }
  ...
}
当我将providerbcprov-jdk15on-152.jar移动到$JAVA_HOME/jre/lib/ext中,并将以下行插入到$JAVA_HOME/jre/lib/security/JAVA.security文件中时:

  security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
当然,在我的例子中,提供者索引是11,因为10是最后一个提供者,所以在你的例子中,它可以是不同的

我已经从我的webapp中删除了bcprov-jdk15on-152.jar文件,一切都很好


因此,我认为Einar的回答是恰当的。

第一个应用程序中有什么问题?这可能是解决这个难题的一条非常重要的信息,它发生在初始化spring上下文的各种场景中。出于测试目的,我在ContextLoaderListener.contextInitialized()实现中强制执行空指针异常。另一种可能是在DigestFactory完全初始化之前调用getDigest。这是不应该发生的,但是Tomcat做了一些奇怪的事情,另外,当ThingA在ThingB的初始化器中初始化ThingB在ThingC的初始化器中初始化时,可能会发生一些“有趣”的场景…@quarts这对我来说没有意义。App2也加载该类,并且该类存在,只有其字段为空。但是,由于这没有任何意义,我尝试了你的建议,但它并没有改变行为。
  security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider